diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 5d2ad8b5c05d..2fabb3c891ad 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -28,7 +28,10 @@ """ from __future__ import division, print_function -import os, warnings, time, io +import os +import warnings +import time +import io import numpy as np import matplotlib.cbook as cbook @@ -41,7 +44,6 @@ from matplotlib._pylab_helpers import Gcf from matplotlib.transforms import Bbox, TransformedBbox, Affine2D -import cStringIO import matplotlib.tight_bbox as tight_bbox import matplotlib.textpath as textpath @@ -55,6 +57,7 @@ _backend_d = {} + def register_backend(format, backend_class): _backend_d[format] = backend_class @@ -111,7 +114,6 @@ def mainloop(self): pass - class RendererBase: """An abstract base class to handle drawing/rendering operations. @@ -156,7 +158,8 @@ def draw_path(self, gc, path, transform, rgbFace=None): """ raise NotImplementedError - def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): + def draw_markers(self, gc, marker_path, marker_trans, path, + trans, rgbFace=None): """ Draws a marker at each of the vertices in path. This includes all vertices, including control points on curves. To avoid @@ -179,9 +182,10 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None) """ for vertices, codes in path.iter_segments(trans, simplify=False): if len(vertices): - x,y = vertices[-2:] + x, y = vertices[-2:] self.draw_path(gc, marker_path, - marker_trans + transforms.Affine2D().translate(x, y), + marker_trans + + transforms.Affine2D().translate(x, y), rgbFace) def draw_path_collection(self, gc, master_transform, paths, all_transforms, @@ -211,15 +215,16 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, """ path_ids = [] for path, transform in self._iter_collection_raw_paths( - master_transform, paths, all_transforms): + master_transform, paths, all_transforms): path_ids.append((path, transform)) for xo, yo, path_id, gc0, rgbFace in self._iter_collection( gc, master_transform, all_transforms, path_ids, offsets, offsetTrans, facecolors, edgecolors, linewidths, linestyles, - antialiaseds, urls, offset_position): + antialiaseds, urls, offset_position): path, transform = path_id - transform = transforms.Affine2D(transform.get_matrix()).translate(xo, yo) + transform = transforms.Affine2D( + transform.get_matrix()).translate(xo, yo) self.draw_path(gc0, path, transform, rgbFace) def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight, @@ -289,9 +294,9 @@ def _iter_collection_raw_paths(self, master_transform, paths, The backend should take each yielded path and transform and create an object that can be referenced (reused) later. """ - Npaths = len(paths) + Npaths = len(paths) Ntransforms = len(all_transforms) - N = max(Npaths, Ntransforms) + N = max(Npaths, Ntransforms) if Npaths == 0: return @@ -333,15 +338,15 @@ def _iter_collection(self, gc, master_transform, all_transforms, use for filling the path. """ Ntransforms = len(all_transforms) - Npaths = len(path_ids) - Noffsets = len(offsets) - N = max(Npaths, Noffsets) + Npaths = len(path_ids) + Noffsets = len(offsets) + N = max(Npaths, Noffsets) Nfacecolors = len(facecolors) Nedgecolors = len(edgecolors) Nlinewidths = len(linewidths) Nlinestyles = len(linestyles) - Naa = len(antialiaseds) - Nurls = len(urls) + Naa = len(antialiaseds) + Nurls = len(urls) if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0: return @@ -364,7 +369,8 @@ def _iter_collection(self, gc, master_transform, all_transforms, xo, yo = toffsets[i % Noffsets] if offset_position == 'data': if Ntransforms: - transform = all_transforms[i % Ntransforms] + master_transform + transform = (all_transforms[i % Ntransforms] + + master_transform) else: transform = master_transform xo, yo = transform.transform_point((xo, yo)) @@ -375,14 +381,14 @@ def _iter_collection(self, gc, master_transform, all_transforms, rgbFace = facecolors[i % Nfacecolors] if Nedgecolors: fg = edgecolors[i % Nedgecolors] - if Nfacecolors == 0 and len(fg)==4: + if Nfacecolors == 0 and len(fg) == 4: gc0.set_alpha(fg[3]) gc0.set_foreground(fg) if Nlinewidths: gc0.set_linewidth(linewidths[i % Nlinewidths]) if Nlinestyles: gc0.set_dashes(*linestyles[i % Nlinestyles]) - if rgbFace is not None and len(rgbFace)==4: + if rgbFace is not None and len(rgbFace) == 4: if rgbFace[3] == 0: rgbFace = None else: @@ -499,24 +505,25 @@ def _get_text_path_transform(self, x, y, s, prop, angle, ismath): fontsize = self.points_to_pixels(prop.get_size_in_points()) if ismath == "TeX": - verts, codes = text2path.get_text_path(prop, s, ismath=False, usetex=True) + verts, codes = text2path.get_text_path(prop, s, ismath=False, + usetex=True) else: - verts, codes = text2path.get_text_path(prop, s, ismath=ismath, usetex=False) + verts, codes = text2path.get_text_path(prop, s, ismath=ismath, + usetex=False) path = Path(verts, codes) - angle = angle/180.*3.141592 + angle = angle / 180. * 3.141592 if self.flipy(): - transform = Affine2D().scale(fontsize/text2path.FONT_SCALE, - fontsize/text2path.FONT_SCALE).\ - rotate(angle).translate(x, self.height-y) + transform = Affine2D().scale(fontsize / text2path.FONT_SCALE, + fontsize / text2path.FONT_SCALE) + transform = transform.rotate(angle).translate(x, self.height - y) else: - transform = Affine2D().scale(fontsize/text2path.FONT_SCALE, - fontsize/text2path.FONT_SCALE).\ - rotate(angle).translate(x, y) + transform = Affine2D().scale(fontsize / text2path.FONT_SCALE, + fontsize / text2path.FONT_SCALE) + transform = transform.rotate(angle).translate(x, y) return path, transform - def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath): """ draw the text by converting them to paths using textpath module. @@ -534,20 +541,20 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath): If True, use mathtext parser. If "TeX", use *usetex* mode. """ - path, transform = self._get_text_path_transform(x, y, s, prop, angle, ismath) + path, transform = self._get_text_path_transform( + x, y, s, prop, angle, ismath) color = gc.get_rgb()[:3] gc.set_linewidth(0.0) self.draw_path(gc, path, transform, rgbFace=color) - def get_text_width_height_descent(self, s, prop, ismath): """ get the width and height, and the offset from the bottom to the baseline (descent), in display coords of the string s with :class:`~matplotlib.font_manager.FontProperties` prop """ - if ismath=='TeX': + if ismath == 'TeX': # todo: handle props size = prop.get_size_in_points() texmanager = self._text2path.get_texmanager() @@ -557,17 +564,16 @@ def get_text_width_height_descent(self, s, prop, ismath): return w, h, d dpi = self.points_to_pixels(72) - fontscale = self._text2path.FONT_SCALE if ismath: - width, height, descent, glyphs, rects = \ - self._text2path.mathtext_parser.parse(s, dpi, prop) - return width, height, descent + dims = self._text2path.mathtext_parser.parse(s, dpi, prop) + return dims[0:3] # return width, height, descent flags = self._text2path._get_hinting_flag() font = self._text2path._get_font(prop) size = prop.get_size_in_points() font.set_size(size, dpi) - font.set_text(s, 0.0, flags=flags) # the width and height of unrotated string + # the width and height of unrotated string + font.set_text(s, 0.0, flags=flags) w, h = font.get_width_height() d = font.get_descent() w /= 64.0 # convert from subpixels @@ -575,7 +581,6 @@ def get_text_width_height_descent(self, s, prop, ismath): d /= 64.0 return w, h, d - def flipy(self): """ Return true if y small numbers are top for renderer Is used @@ -597,7 +602,6 @@ def get_texmanager(self): self._texmanager = TexManager() return self._texmanager - def new_gc(self): """ Return an instance of a :class:`GraphicsContextBase` @@ -662,15 +666,15 @@ class GraphicsContextBase: # a mapping from dash styles to suggested offset, dash pairs dashd = { - 'solid' : (None, None), - 'dashed' : (0, (6.0, 6.0)), - 'dashdot' : (0, (3.0, 5.0, 1.0, 5.0)), - 'dotted' : (0, (1.0, 3.0)), - } + 'solid': (None, None), + 'dashed': (0, (6.0, 6.0)), + 'dashdot': (0, (3.0, 5.0, 1.0, 5.0)), + 'dotted': (0, (1.0, 3.0)), + } def __init__(self): self._alpha = 1.0 - self._forced_alpha = False # if True, _alpha overrides A from RGBA + self._forced_alpha = False # if True, _alpha overrides A from RGBA self._antialiased = 1 # use 0,1 not True, False for extension code self._capstyle = 'butt' self._cliprect = None @@ -728,7 +732,8 @@ def get_capstyle(self): def get_clip_rectangle(self): """ - Return the clip rectangle as a :class:`~matplotlib.transforms.Bbox` instance + Return the clip rectangle as a :class:`~matplotlib.transforms.Bbox` + instance """ return self._cliprect @@ -824,8 +829,10 @@ def set_antialiased(self, b): """ # use 0, 1 to make life easier on extension code trying to read the gc - if b: self._antialiased = 1 - else: self._antialiased = 0 + if b: + self._antialiased = 1 + else: + self._antialiased = 0 def set_capstyle(self, cs): """ @@ -858,7 +865,8 @@ def set_dashes(self, dash_offset, dash_list): is the offset (usually 0). *dash_list* - specifies the on-off sequence as points. ``(None, None)`` specifies a solid line + specifies the on-off sequence as points. + ``(None, None)`` specifies a solid line """ self._dashes = dash_offset, dash_list @@ -1021,7 +1029,7 @@ def __init__(self, interval=None, callbacks=None): if callbacks is None: self.callbacks = [] else: - self.callbacks = callbacks[:] # Create a copy + self.callbacks = callbacks[:] # Create a copy if interval is None: self._interval = 1000 @@ -1113,10 +1121,10 @@ def _on_timer(self): can return False if they should not be called any more. If there are no callbacks, the timer is automatically stopped. ''' - for func,args,kwargs in self.callbacks: + for func, args, kwargs in self.callbacks: ret = func(*args, **kwargs) - if ret == False: - self.callbacks.remove((func,args,kwargs)) + if not ret: + self.callbacks.remove((func, args, kwargs)) if len(self.callbacks) == 0: self.stop() @@ -1139,11 +1147,12 @@ class Event: """ - def __init__(self, name, canvas,guiEvent=None): + def __init__(self, name, canvas, guiEvent=None): self.name = name self.canvas = canvas self.guiEvent = guiEvent + class IdleEvent(Event): """ An event triggered by the GUI backend when it is idle -- useful @@ -1151,11 +1160,13 @@ class IdleEvent(Event): """ pass + class DrawEvent(Event): """ An event triggered by a draw operation on the canvas - In addition to the :class:`Event` attributes, the following event attributes are defined: + In addition to the :class:`Event` attributes, the following event + attributes are defined: *renderer* the :class:`RendererBase` instance for the draw event @@ -1165,11 +1176,13 @@ def __init__(self, name, canvas, renderer): Event.__init__(self, name, canvas) self.renderer = renderer + class ResizeEvent(Event): """ An event triggered by a canvas resize - In addition to the :class:`Event` attributes, the following event attributes are defined: + In addition to the :class:`Event` attributes, the following event + attributes are defined: *width* width of the canvas in pixels @@ -1182,11 +1195,13 @@ def __init__(self, name, canvas): Event.__init__(self, name, canvas) self.width, self.height = canvas.get_width_height() + class CloseEvent(Event): """ An event triggered by a figure being closed - In addition to the :class:`Event` attributes, the following event attributes are defined: + In addition to the :class:`Event` attributes, the following event + attributes are defined: """ def __init__(self, name, canvas, guiEvent=None): Event.__init__(self, name, canvas, guiEvent) @@ -1218,25 +1233,23 @@ class LocationEvent(Event): y coord of mouse in data coords """ - x = None # x position - pixels from left of canvas - y = None # y position - pixels from right of canvas - inaxes = None # the Axes instance if mouse us over axes - xdata = None # x coord of mouse in data coords - ydata = None # y coord of mouse in data coords + x = None # x position - pixels from left of canvas + y = None # y position - pixels from right of canvas + inaxes = None # the Axes instance if mouse us over axes + xdata = None # x coord of mouse in data coords + ydata = None # y coord of mouse in data coords # the last event that was triggered before this one lastevent = None - def __init__(self, name, canvas, x, y,guiEvent=None): + def __init__(self, name, canvas, x, y, guiEvent=None): """ *x*, *y* in figure coords, 0,0 = bottom, left """ - Event.__init__(self, name, canvas,guiEvent=guiEvent) + Event.__init__(self, name, canvas, guiEvent=guiEvent) self.x = x self.y = y - - if x is None or y is None: # cannot check if event was in axes if no x,y info self.inaxes = None @@ -1245,28 +1258,30 @@ def __init__(self, name, canvas, x, y,guiEvent=None): # Find all axes containing the mouse if self.canvas.mouse_grabber is None: - axes_list = [a for a in self.canvas.figure.get_axes() if a.in_axes(self)] + axes_list = [a for a in self.canvas.figure.get_axes() + if a.in_axes(self)] else: axes_list = [self.canvas.mouse_grabber] - if len(axes_list) == 0: # None found + if len(axes_list) == 0: # None found self.inaxes = None self._update_enter_leave() return - elif (len(axes_list) > 1): # Overlap, get the highest zorder + elif (len(axes_list) > 1): # Overlap, get the highest zorder axes_list.sort(key=lambda x: x.zorder) - self.inaxes = axes_list[-1] # Use the highest zorder - else: # Just found one hit + self.inaxes = axes_list[-1] # Use the highest zorder + else: # Just found one hit self.inaxes = axes_list[0] try: - xdata, ydata = self.inaxes.transData.inverted().transform_point((x, y)) + trans = self.inaxes.transData.inverted() + xdata, ydata = trans.transform_point((x, y)) except ValueError: - self.xdata = None - self.ydata = None + self.xdata = None + self.ydata = None else: - self.xdata = xdata - self.ydata = ydata + self.xdata = xdata + self.ydata = ydata self._update_enter_leave() @@ -1274,7 +1289,7 @@ def _update_enter_leave(self): 'process the figure/axes enter leave events' if LocationEvent.lastevent is not None: last = LocationEvent.lastevent - if last.inaxes!=self.inaxes: + if last.inaxes != self.inaxes: # process axes enter/leave events try: if last.inaxes is not None: @@ -1299,8 +1314,10 @@ def _update_enter_leave(self): class MouseEvent(LocationEvent): """ - A mouse event ('button_press_event', 'button_release_event', 'scroll_event', - 'motion_notify_event'). + A mouse event ('button_press_event', + 'button_release_event', + 'scroll_event', + 'motion_notify_event'). In addition to the :class:`Event` and :class:`LocationEvent` attributes, the following attributes are defined: @@ -1325,14 +1342,14 @@ def on_press(event): cid = fig.canvas.mpl_connect('button_press_event', on_press) """ - x = None # x position - pixels from left of canvas - y = None # y position - pixels from right of canvas - button = None # button pressed None, 1, 2, 3 - dblclick = None # whether or not the event is the result of a double click - inaxes = None # the Axes instance if mouse us over axes - xdata = None # x coord of mouse in data coords - ydata = None # y coord of mouse in data coords - step = None # scroll steps for scroll events + x = None # x position - pixels from left of canvas + y = None # y position - pixels from right of canvas + button = None # button pressed None, 1, 2, 3 + dblclick = None # whether or not the event is the result of a double click + inaxes = None # the Axes instance if mouse us over axes + xdata = None # x coord of mouse in data coords + ydata = None # y coord of mouse in data coords + step = None # scroll steps for scroll events def __init__(self, name, canvas, x, y, button=None, key=None, step=0, dblclick=False, guiEvent=None): @@ -1349,7 +1366,8 @@ def __init__(self, name, canvas, x, y, button=None, key=None, def __str__(self): return ("MPL MouseEvent: xy=(%d,%d) xydata=(%s,%s) button=%d " + "dblclick=%s inaxes=%s") % (self.x, self.y, self.xdata, - self.ydata, self.button, self.dblclick, self.inaxes) + self.ydata, self.button, + self.dblclick, self.inaxes) class PickEvent(Event): @@ -1385,7 +1403,8 @@ def on_pick(event): cid = fig.canvas.mpl_connect('pick_event', on_pick) """ - def __init__(self, name, canvas, mouseevent, artist, guiEvent=None, **kwargs): + def __init__(self, name, canvas, mouseevent, artist, + guiEvent=None, **kwargs): Event.__init__(self, name, canvas, guiEvent) self.mouseevent = mouseevent self.artist = artist @@ -1455,7 +1474,7 @@ class FigureCanvasBase(object): 'axes_enter_event', 'axes_leave_event', 'close_event' - ] + ] def __init__(self, figure): figure.set_canvas(self) @@ -1463,16 +1482,16 @@ def __init__(self, figure): # a dictionary from event name to a dictionary that maps cid->func self.callbacks = cbook.CallbackRegistry() self.widgetlock = widgets.LockDraw() - self._button = None # the button pressed - self._key = None # the key pressed + self._button = None # the button pressed + self._key = None # the key pressed self._lastx, self._lasty = None, None - self.button_pick_id = self.mpl_connect('button_press_event',self.pick) - self.scroll_pick_id = self.mpl_connect('scroll_event',self.pick) - self.mouse_grabber = None # the axes currently grabbing mouse + self.button_pick_id = self.mpl_connect('button_press_event', self.pick) + self.scroll_pick_id = self.mpl_connect('scroll_event', self.pick) + self.mouse_grabber = None # the axes currently grabbing mouse self.toolbar = None # NavigationToolbar2 will set me if False: ## highlight the artists that are hit - self.mpl_connect('motion_notify_event',self.onHilite) + self.mpl_connect('motion_notify_event', self.onHilite) ## delete the artists that are clicked on #self.mpl_disconnect(self.button_pick_id) #self.mpl_connect('button_press_event',self.onRemove) @@ -1488,19 +1507,19 @@ def onRemove(self, ev): def sort_artists(artists): # This depends on stable sort and artists returned # from get_children in z order. - L = [ (h.zorder, h) for h in artists ] + L = [(h.zorder, h) for h in artists] L.sort() - return [ h for zorder, h in L ] + return [h for zorder, h in L] # Find the top artist under the cursor under = sort_artists(self.figure.hitlist(ev)) h = None - if under: h = under[-1] + if under: + h = under[-1] # Try deleting that artist, or its parent if you # can't delete the artist while h: - print("Removing",h) if h.remove(): self.draw_idle() break @@ -1519,7 +1538,8 @@ def onHilite(self, ev): canvas.mpl_connect('motion_notify_event',canvas.onHilite) """ - if not hasattr(self,'_active'): self._active = dict() + if not hasattr(self, '_active'): + self._active = dict() under = self.figure.hitlist(ev) enter = [a for a in under if a not in self._active] @@ -1529,9 +1549,9 @@ def onHilite(self, ev): #print "leaving:",[str(a) for a in leave] # On leave restore the captured colour for a in leave: - if hasattr(a,'get_color'): + if hasattr(a, 'get_color'): a.set_color(self._active[a]) - elif hasattr(a,'get_edgecolor'): + elif hasattr(a, 'get_edgecolor'): a.set_edgecolor(self._active[a][0]) a.set_facecolor(self._active[a][1]) del self._active[a] @@ -1540,18 +1560,20 @@ def onHilite(self, ev): # be done first in case the parent recolouring affects # the child. for a in enter: - if hasattr(a,'get_color'): + if hasattr(a, 'get_color'): self._active[a] = a.get_color() - elif hasattr(a,'get_edgecolor'): - self._active[a] = (a.get_edgecolor(),a.get_facecolor()) - else: self._active[a] = None + elif hasattr(a, 'get_edgecolor'): + self._active[a] = (a.get_edgecolor(), a.get_facecolor()) + else: + self._active[a] = None for a in enter: - if hasattr(a,'get_color'): + if hasattr(a, 'get_color'): a.set_color('red') - elif hasattr(a,'get_edgecolor'): + elif hasattr(a, 'get_edgecolor'): a.set_edgecolor('red') a.set_facecolor('lightblue') - else: self._active[a] = None + else: + self._active[a] = None self.draw_idle() def pick(self, mouseevent): @@ -1614,7 +1636,8 @@ def key_press_event(self, key, guiEvent=None): """ self._key = key s = 'key_press_event' - event = KeyEvent(s, self, key, self._lastx, self._lasty, guiEvent=guiEvent) + event = KeyEvent( + s, self, key, self._lastx, self._lasty, guiEvent=guiEvent) self.callbacks.process(s, event) def key_release_event(self, key, guiEvent=None): @@ -1623,7 +1646,8 @@ def key_release_event(self, key, guiEvent=None): 'key_release_event' with a :class:`KeyEvent` """ s = 'key_release_event' - event = KeyEvent(s, self, key, self._lastx, self._lasty, guiEvent=guiEvent) + event = KeyEvent( + s, self, key, self._lastx, self._lasty, guiEvent=guiEvent) self.callbacks.process(s, event) self._key = None @@ -1654,7 +1678,6 @@ def scroll_event(self, x, y, step, guiEvent=None): step=step, guiEvent=guiEvent) self.callbacks.process(s, mouseevent) - def button_press_event(self, x, y, button, dblclick=False, guiEvent=None): """ Backend derived classes should call this function on any mouse @@ -1774,7 +1797,8 @@ def release_mouse(self, ax): """ Release the mouse grab held by the axes, ax. Usually called by the widgets. - It is ok to call this even if you ax doesn't have the mouse grab currently. + It is ok to call this even if you ax doesn't have the mouse + grab currently. """ if self.mouse_grabber is ax: self.mouse_grabber = None @@ -1811,12 +1835,11 @@ def get_width_height(self): 'pdf': 'Portable Document Format', 'pgf': 'LaTeX PGF Figure', 'png': 'Portable Network Graphics', - 'ps' : 'Postscript', + 'ps': 'Postscript', 'raw': 'Raw RGBA bitmap', 'rgba': 'Raw RGBA bitmap', 'svg': 'Scalable Vector Graphics', - 'svgz': 'Scalable Vector Graphics' - } + 'svgz': 'Scalable Vector Graphics'} # All of these print_* functions do a lazy import because # a) otherwise we'd have cyclical imports, since all of these @@ -1830,53 +1853,55 @@ def get_width_height(self): # >>> list(matplotlib.tests.test_spines.test_spines_axes_positions())[0][0]() def print_emf(self, *args, **kwargs): - from backends.backend_emf import FigureCanvasEMF # lazy import + from backends.backend_emf import FigureCanvasEMF # lazy import emf = self.switch_backends(FigureCanvasEMF) return emf.print_emf(*args, **kwargs) def print_eps(self, *args, **kwargs): - from backends.backend_ps import FigureCanvasPS # lazy import + from backends.backend_ps import FigureCanvasPS # lazy import ps = self.switch_backends(FigureCanvasPS) return ps.print_eps(*args, **kwargs) def print_pdf(self, *args, **kwargs): - from backends.backend_pdf import FigureCanvasPdf # lazy import + from backends.backend_pdf import FigureCanvasPdf # lazy import pdf = self.switch_backends(FigureCanvasPdf) return pdf.print_pdf(*args, **kwargs) def print_pgf(self, *args, **kwargs): - from backends.backend_pgf import FigureCanvasPgf # lazy import + from backends.backend_pgf import FigureCanvasPgf # lazy import pgf = self.switch_backends(FigureCanvasPgf) return pgf.print_pgf(*args, **kwargs) def print_png(self, *args, **kwargs): - from backends.backend_agg import FigureCanvasAgg # lazy import + from backends.backend_agg import FigureCanvasAgg # lazy import agg = self.switch_backends(FigureCanvasAgg) return agg.print_png(*args, **kwargs) def print_ps(self, *args, **kwargs): - from backends.backend_ps import FigureCanvasPS # lazy import + from backends.backend_ps import FigureCanvasPS # lazy import ps = self.switch_backends(FigureCanvasPS) return ps.print_ps(*args, **kwargs) def print_raw(self, *args, **kwargs): - from backends.backend_agg import FigureCanvasAgg # lazy import + from backends.backend_agg import FigureCanvasAgg # lazy import agg = self.switch_backends(FigureCanvasAgg) return agg.print_raw(*args, **kwargs) print_bmp = print_rgb = print_raw def print_svg(self, *args, **kwargs): - from backends.backend_svg import FigureCanvasSVG # lazy import + from backends.backend_svg import FigureCanvasSVG # lazy import svg = self.switch_backends(FigureCanvasSVG) return svg.print_svg(*args, **kwargs) def print_svgz(self, *args, **kwargs): - from backends.backend_svg import FigureCanvasSVG # lazy import + from backends.backend_svg import FigureCanvasSVG # lazy import svg = self.switch_backends(FigureCanvasSVG) return svg.print_svgz(*args, **kwargs) if _has_pil: - filetypes['jpg'] = filetypes['jpeg'] = 'Joint Photographic Experts Group' + filetypes['jpg'] = 'Joint Photographic Experts Group' + filetypes['jpeg'] = filetypes['jpg'] + def print_jpg(self, filename_or_obj, *args, **kwargs): """ Supported kwargs: @@ -1893,10 +1918,11 @@ def print_jpg(self, filename_or_obj, *args, **kwargs): *progressive*: If present, indicates that this image should be stored as a progressive JPEG file. """ - from backends.backend_agg import FigureCanvasAgg # lazy import + from backends.backend_agg import FigureCanvasAgg # lazy import agg = self.switch_backends(FigureCanvasAgg) buf, size = agg.print_to_buffer() - if kwargs.pop("dryrun", False): return + if kwargs.pop("dryrun", False): + return image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) options = cbook.restrict_dict(kwargs, ['quality', 'optimize', 'progressive']) @@ -1904,11 +1930,13 @@ def print_jpg(self, filename_or_obj, *args, **kwargs): print_jpeg = print_jpg filetypes['tif'] = filetypes['tiff'] = 'Tagged Image File Format' + def print_tif(self, filename_or_obj, *args, **kwargs): - from backends.backend_agg import FigureCanvasAgg # lazy import + from backends.backend_agg import FigureCanvasAgg # lazy import agg = self.switch_backends(FigureCanvasAgg) buf, size = agg.print_to_buffer() - if kwargs.pop("dryrun", False): return + if kwargs.pop("dryrun", False): + return image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) dpi = (self.figure.dpi, self.figure.dpi) return image.save(filename_or_obj, format='tiff', @@ -2014,7 +2042,6 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', if dpi is None: dpi = rcParams['savefig.dpi'] - origDPI = self.figure.dpi origfacecolor = self.figure.get_facecolor() origedgecolor = self.figure.get_edgecolor() @@ -2027,7 +2054,6 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', if bbox_inches is None: bbox_inches = rcParams['savefig.bbox'] - if bbox_inches: # call adjust_bbox to save only the given area if bbox_inches == "tight": @@ -2057,16 +2083,15 @@ def print_figure(self, filename, dpi=None, facecolor='w', edgecolor='w', if bbox_extra_artists is None: bbox_extra_artists = self.figure.get_default_bbox_extra_artists() - bb = [a.get_window_extent(renderer) for a in bbox_extra_artists] + bb = [a.get_window_extent(renderer) + for a in bbox_extra_artists] - bbox_filtered = [b for b in bb if b.width!=0 or b.height!=0] + bbox_filtered = [b for b in bb + if b.width != 0 or b.height != 0] if bbox_filtered: _bbox = Bbox.union(bbox_filtered) - - bbox_inches1 = TransformedBbox(_bbox, - Affine2D().scale(1./self.figure.dpi)) - - bbox_inches = Bbox.union([bbox_inches, bbox_inches1]) + trans = Affine2D().scale(1.0 / self.figure.dpi) + bbox_inches = TransformedBbox(_bbox, trans) pad = kwargs.pop("pad_inches", None) if pad is None: @@ -2208,9 +2233,10 @@ def mpl_disconnect(self, cid): def new_timer(self, *args, **kwargs): """ - Creates a new backend-specific subclass of :class:`backend_bases.Timer`. - This is useful for getting periodic events through the backend's native - event loop. Implemented only for backends with GUIs. + Creates a new backend-specific subclass of + :class:`backend_bases.Timer`. This is useful for getting periodic + events through the backend's native event loop. Implemented only for + backends with GUIs. optional arguments: @@ -2229,7 +2255,7 @@ def flush_events(self): """ raise NotImplementedError - def start_event_loop(self,timeout): + def start_event_loop(self, timeout): """ Start an event loop. This is used to start a blocking event loop so that interactive functions, such as ginput and @@ -2251,7 +2277,7 @@ def stop_event_loop(self): """ raise NotImplementedError - def start_event_loop_default(self,timeout=0): + def start_event_loop_default(self, timeout=0): """ Start an event loop. This is used to start a blocking event loop so that interactive functions, such as ginput and @@ -2274,13 +2300,14 @@ def start_event_loop_default(self,timeout=0): """ str = "Using default event loop until function specific" str += " to this GUI is implemented" - warnings.warn(str,DeprecationWarning) + warnings.warn(str, DeprecationWarning) - if timeout <= 0: timeout = np.inf + if timeout <= 0: + timeout = np.inf timestep = 0.01 counter = 0 self._looping = True - while self._looping and counter*timestep < timeout: + while self._looping and counter * timestep < timeout: self.flush_events() time.sleep(timestep) counter += 1 @@ -2389,25 +2416,27 @@ def key_press_handler(event, canvas, toolbar=None): ax.set_xscale('log') ax.figure.canvas.draw() - elif (event.key.isdigit() and event.key!='0') or event.key in all: + elif (event.key.isdigit() and event.key != '0') or event.key in all: # keys in list 'all' enables all axes (default key 'a'), # otherwise if key is a number only enable this particular axes # if it was the axes, where the event was raised if not (event.key in all): - n = int(event.key)-1 + n = int(event.key) - 1 for i, a in enumerate(canvas.figure.get_axes()): # consider axes, in which the event was raised # FIXME: Why only this axes? if event.x is not None and event.y is not None \ - and a.in_axes(event): + and a.in_axes(event): if event.key in all: a.set_navigate(True) else: - a.set_navigate(i==n) + a.set_navigate(i == n) + class NonGuiException(Exception): pass + class FigureManagerBase: """ Helper class for pyplot mode, wraps everything up into a neat bundle @@ -2428,7 +2457,8 @@ def __init__(self, canvas, num): self.key_press_handler_id = self.canvas.mpl_connect('key_press_event', self.key_press) """ - The returned id from connecting the default key handler via :meth:`FigureCanvasBase.mpl_connnect`. + The returned id from connecting the default key handler via + :meth:`FigureCanvasBase.mpl_connnect`. To disable default key press handling:: @@ -2551,7 +2581,7 @@ class NavigationToolbar2(object): (None, None, None, None), ('Subplots', 'Configure subplots', 'subplots', 'configure_subplots'), ('Save', 'Save the figure', 'filesave', 'save_figure'), - ) + ) def __init__(self, canvas): self.canvas = canvas @@ -2559,18 +2589,21 @@ def __init__(self, canvas): # a dict from axes index to a list of view limits self._views = cbook.Stack() self._positions = cbook.Stack() # stack of subplot positions - self._xypress = None # the location and axis info at the time of the press + self._xypress = None # the location and axis info at the time + # of the press self._idPress = None self._idRelease = None self._active = None self._lastCursor = None self._init_toolbar() - self._idDrag=self.canvas.mpl_connect('motion_notify_event', self.mouse_move) + self._idDrag = self.canvas.mpl_connect( + 'motion_notify_event', self.mouse_move) self._ids_zoom = [] self._zoom_mode = None - self._button_pressed = None # determined by the button pressed at start + self._button_pressed = None # determined by the button pressed + # at start self.mode = '' # a mode string for the status bar self.set_history_buttons() @@ -2635,11 +2668,11 @@ def mouse_move(self, event): self.set_cursor(cursors.POINTER) self._lastCursor = cursors.POINTER else: - if self._active=='ZOOM': + if self._active == 'ZOOM': if self._lastCursor != cursors.SELECT_REGION: self.set_cursor(cursors.SELECT_REGION) self._lastCursor = cursors.SELECT_REGION - elif (self._active=='PAN' and + elif (self._active == 'PAN' and self._lastCursor != cursors.MOVE): self.set_cursor(cursors.MOVE) @@ -2656,9 +2689,10 @@ def mouse_move(self, event): self.set_message('%s, %s' % (self.mode, s)) else: self.set_message(s) - else: self.set_message(self.mode) + else: + self.set_message(self.mode) - def pan(self,*args): + def pan(self, *args): """Activate the pan/zoom tool. pan with left button, zoom with right""" # set the pointer icon and button press funcs to the # appropriate callbacks @@ -2698,51 +2732,53 @@ def press_pan(self, event): """the press mouse button in pan/zoom mode callback""" if event.button == 1: - self._button_pressed=1 - elif event.button == 3: - self._button_pressed=3 + self._button_pressed = 1 + elif event.button == 3: + self._button_pressed = 3 else: - self._button_pressed=None + self._button_pressed = None return x, y = event.x, event.y # push the current view to define home if stack is empty - if self._views.empty(): self.push_current() + if self._views.empty(): + self.push_current() - self._xypress=[] + self._xypress = [] for i, a in enumerate(self.canvas.figure.get_axes()): if (x is not None and y is not None and a.in_axes(event) and - a.get_navigate() and a.can_pan()) : + a.get_navigate() and a.can_pan()): a.start_pan(x, y, event.button) self._xypress.append((a, i)) self.canvas.mpl_disconnect(self._idDrag) - self._idDrag=self.canvas.mpl_connect('motion_notify_event', - self.drag_pan) + self._idDrag = self.canvas.mpl_connect('motion_notify_event', + self.drag_pan) self.press(event) def press_zoom(self, event): """the press mouse button in zoom to rect mode callback""" if event.button == 1: - self._button_pressed=1 - elif event.button == 3: - self._button_pressed=3 + self._button_pressed = 1 + elif event.button == 3: + self._button_pressed = 3 else: - self._button_pressed=None + self._button_pressed = None return x, y = event.x, event.y # push the current view to define home if stack is empty - if self._views.empty(): self.push_current() + if self._views.empty(): + self.push_current() - self._xypress=[] + self._xypress = [] for i, a in enumerate(self.canvas.figure.get_axes()): if (x is not None and y is not None and a.in_axes(event) and - a.get_navigate() and a.can_zoom()) : - self._xypress.append(( x, y, a, i, a.viewLim.frozen(), - a.transData.frozen() )) + a.get_navigate() and a.can_zoom()): + self._xypress.append((x, y, a, i, a.viewLim.frozen(), + a.transData.frozen())) id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom) id2 = self.canvas.mpl_connect('key_press_event', @@ -2765,15 +2801,16 @@ def _switch_off_zoom_mode(self, event): def push_current(self): """push the current view limits and position onto the stack""" - lims = []; pos = [] + lims = [] + pos = [] for a in self.canvas.figure.get_axes(): xmin, xmax = a.get_xlim() ymin, ymax = a.get_ylim() - lims.append( (xmin, xmax, ymin, ymax) ) + lims.append((xmin, xmax, ymin, ymax)) # Store both the original and modified positions - pos.append( ( - a.get_position(True).frozen(), - a.get_position().frozen() ) ) + pos.append(( + a.get_position(True).frozen(), + a.get_position().frozen())) self._views.push(lims) self._positions.push(pos) self.set_history_buttons() @@ -2788,12 +2825,14 @@ def release_pan(self, event): if self._button_pressed is None: return self.canvas.mpl_disconnect(self._idDrag) - self._idDrag=self.canvas.mpl_connect('motion_notify_event', self.mouse_move) + self._idDrag = self.canvas.mpl_connect( + 'motion_notify_event', self.mouse_move) for a, ind in self._xypress: a.end_pan() - if not self._xypress: return + if not self._xypress: + return self._xypress = [] - self._button_pressed=None + self._button_pressed = None self.push_current() self.release(event) self.draw() @@ -2834,7 +2873,8 @@ def release_zoom(self, event): self.canvas.mpl_disconnect(zoom_id) self._ids_zoom = [] - if not self._xypress: return + if not self._xypress: + return last_a = [] @@ -2842,7 +2882,7 @@ def release_zoom(self, event): x, y = event.x, event.y lastx, lasty, a, ind, lim, trans = cur_xypress # ignore singular clicks - 5 pixels is a threshold - if abs(x-lastx)<5 or abs(y-lasty)<5: + if abs(x - lastx) < 5 or abs(y - lasty) < 5: self._xypress = None self.release(event) self.draw() @@ -2852,46 +2892,64 @@ def release_zoom(self, event): # zoom to rect inverse = a.transData.inverted() - lastx, lasty = inverse.transform_point( (lastx, lasty) ) - x, y = inverse.transform_point( (x, y) ) - Xmin,Xmax=a.get_xlim() - Ymin,Ymax=a.get_ylim() + lastx, lasty = inverse.transform_point((lastx, lasty)) + x, y = inverse.transform_point((x, y)) + Xmin, Xmax = a.get_xlim() + Ymin, Ymax = a.get_ylim() # detect twinx,y axes and avoid double zooming twinx, twiny = False, False if last_a: for la in last_a: - if a.get_shared_x_axes().joined(a,la): twinx=True - if a.get_shared_y_axes().joined(a,la): twiny=True + if a.get_shared_x_axes().joined(a, la): + twinx = True + if a.get_shared_y_axes().joined(a, la): + twiny = True last_a.append(a) if twinx: x0, x1 = Xmin, Xmax else: if Xmin < Xmax: - if x Xmax: x1=Xmax + if x < lastx: + x0, x1 = x, lastx + else: + x0, x1 = lastx, x + if x0 < Xmin: + x0 = Xmin + if x1 > Xmax: + x1 = Xmax else: - if x>lastx: x0, x1 = x, lastx - else: x0, x1 = lastx, x - if x0 > Xmin: x0=Xmin - if x1 < Xmax: x1=Xmax + if x > lastx: + x0, x1 = x, lastx + else: + x0, x1 = lastx, x + if x0 > Xmin: + x0 = Xmin + if x1 < Xmax: + x1 = Xmax if twiny: y0, y1 = Ymin, Ymax else: if Ymin < Ymax: - if y Ymax: y1=Ymax + if y < lasty: + y0, y1 = y, lasty + else: + y0, y1 = lasty, y + if y0 < Ymin: + y0 = Ymin + if y1 > Ymax: + y1 = Ymax else: - if y>lasty: y0, y1 = y, lasty - else: y0, y1 = lasty, y - if y0 > Ymin: y0=Ymin - if y1 < Ymax: y1=Ymax + if y > lasty: + y0, y1 = y, lasty + else: + y0, y1 = lasty, y + if y0 > Ymin: + y0 = Ymin + if y1 < Ymax: + y1 = Ymax if self._button_pressed == 1: if self._zoom_mode == "x": @@ -2902,22 +2960,22 @@ def release_zoom(self, event): a.set_xlim((x0, x1)) a.set_ylim((y0, y1)) elif self._button_pressed == 3: - if a.get_xscale()=='log': - alpha=np.log(Xmax/Xmin)/np.log(x1/x0) - rx1=pow(Xmin/x0,alpha)*Xmin - rx2=pow(Xmax/x0,alpha)*Xmin + if a.get_xscale() == 'log': + alpha = np.log(Xmax / Xmin) / np.log(x1 / x0) + rx1 = pow(Xmin / x0, alpha) * Xmin + rx2 = pow(Xmax / x0, alpha) * Xmin else: - alpha=(Xmax-Xmin)/(x1-x0) - rx1=alpha*(Xmin-x0)+Xmin - rx2=alpha*(Xmax-x0)+Xmin - if a.get_yscale()=='log': - alpha=np.log(Ymax/Ymin)/np.log(y1/y0) - ry1=pow(Ymin/y0,alpha)*Ymin - ry2=pow(Ymax/y0,alpha)*Ymin + alpha = (Xmax - Xmin) / (x1 - x0) + rx1 = alpha * (Xmin - x0) + Xmin + rx2 = alpha * (Xmax - x0) + Xmin + if a.get_yscale() == 'log': + alpha = np.log(Ymax / Ymin) / np.log(y1 / y0) + ry1 = pow(Ymin / y0, alpha) * Ymin + ry2 = pow(Ymax / y0, alpha) * Ymin else: - alpha=(Ymax-Ymin)/(y1-y0) - ry1=alpha*(Ymin-y0)+Ymin - ry2=alpha*(Ymax-y0)+Ymin + alpha = (Ymax - Ymin) / (y1 - y0) + ry1 = alpha * (Ymin - y0) + Ymin + ry2 = alpha * (Ymax - y0) + Ymin if self._zoom_mode == "x": a.set_xlim((rx1, rx2)) @@ -2959,16 +3017,18 @@ def _update_view(self): """ lims = self._views() - if lims is None: return + if lims is None: + return pos = self._positions() - if pos is None: return + if pos is None: + return for i, a in enumerate(self.canvas.figure.get_axes()): xmin, xmax, ymin, ymax = lims[i] a.set_xlim((xmin, xmax)) a.set_ylim((ymin, ymax)) # Restore both the original and modified positions - a.set_position( pos[i][0], 'original' ) - a.set_position( pos[i][1], 'active' ) + a.set_position(pos[i][0], 'original') + a.set_position(pos[i][1], 'active') self.draw() @@ -2997,16 +3057,18 @@ def zoom(self, *args): self._active = 'ZOOM' if self._idPress is not None: - self._idPress=self.canvas.mpl_disconnect(self._idPress) + self._idPress = self.canvas.mpl_disconnect(self._idPress) self.mode = '' if self._idRelease is not None: - self._idRelease=self.canvas.mpl_disconnect(self._idRelease) + self._idRelease = self.canvas.mpl_disconnect(self._idRelease) self.mode = '' - if self._active: - self._idPress = self.canvas.mpl_connect('button_press_event', self.press_zoom) - self._idRelease = self.canvas.mpl_connect('button_release_event', self.release_zoom) + if self._active: + self._idPress = self.canvas.mpl_connect('button_press_event', + self.press_zoom) + self._idRelease = self.canvas.mpl_connect('button_release_event', + self.release_zoom) self.mode = 'zoom rect' self.canvas.widgetlock(self) else: