diff --git a/doc/api/api_changes/2017-06-03-ES_unique_renderer.rst b/doc/api/api_changes/2017-06-03-ES_unique_renderer.rst new file mode 100644 index 000000000000..3dd58e0a16f3 --- /dev/null +++ b/doc/api/api_changes/2017-06-03-ES_unique_renderer.rst @@ -0,0 +1,11 @@ +Unique identifier added to `RendererBase` classes +````````````````````````````````````````````````` + +Since ``id()`` is not guaranteed to be unique between objects that exist at +different times, a new private property ``_uid`` has been added to +`RendererBase` which is used along with the renderer's ``id()`` to cache +certain expensive operations. + +If a custom renderer does not subclass `RendererBase` or `MixedModeRenderer`, +it is not required to implement this ``_uid`` property, but this may produce +incorrect behavior when the renderers' ``id()`` clashes. diff --git a/doc/api/api_changes/2017-06-11-DB_magnitude_spectrum.rst b/doc/api/api_changes/2017-06-11-DB_magnitude_spectrum.rst new file mode 100644 index 000000000000..67efbb580a99 --- /dev/null +++ b/doc/api/api_changes/2017-06-11-DB_magnitude_spectrum.rst @@ -0,0 +1,31 @@ +Correct scaling of :func:`magnitude_spectrum()` +``````````````````````````````````````````````` + +The functions :func:`matplotlib.mlab.magnitude_spectrum()` and :func:`matplotlib.pyplot.magnitude_spectrum()` implicitly assumed the sum +of windowing function values to be one. In Matplotlib and Numpy the +standard windowing functions are scaled to have maximum value of one, +which usually results in a sum of the order of n/2 for a n-point +signal. Thus the amplitude scaling :func:`magnitude_spectrum()` was +off by that amount when using standard windowing functions (`Bug 8417 +`_ ). Now the +behavior is consistent with :func:`matplotlib.pyplot.psd()` and +:func:`scipy.signal.welch()`. The following example demonstrates the +new and old scaling:: + + import matplotlib.pyplot as plt + import numpy as np + + tau, n = 10, 1024 # 10 second signal with 1024 points + T = tau/n # sampling interval + t = np.arange(n)*T + + a = 4 # amplitude + x = a*np.sin(40*np.pi*t) # 20 Hz sine with amplitude a + + # New correct behavior: Amplitude at 20 Hz is a/2 + plt.magnitude_spectrum(x, Fs=1/T, sides='onesided', scale='linear') + + # Original behavior: Amplitude at 20 Hz is (a/2)*(n/2) for a Hanning window + w = np.hanning(n) # default window is a Hanning window + plt.magnitude_spectrum(x*np.sum(w), Fs=1/T, sides='onesided', scale='linear') + diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 97a90c647664..90a1ef89ad7e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6666,8 +6666,7 @@ def magnitude_spectrum(self, x, Fs=None, Fc=None, window=None, scale : [ 'default' | 'linear' | 'dB' ] The scaling of the values in the *spec*. 'linear' is no scaling. - 'dB' returns the values in dB scale. When *mode* is 'density', - this is dB power (10 * log10). Otherwise this is dB amplitude + 'dB' returns the values in dB scale, i.e., the dB amplitude (20 * log10). 'default' is 'linear'. Fc : integer diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 7948c14de8e9..4462cd620ce6 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -980,21 +980,8 @@ def cla(self): self.xaxis.minor = self._sharex.xaxis.minor x0, x1 = self._sharex.get_xlim() self.set_xlim(x0, x1, emit=False, auto=None) - - # Save the current formatter/locator so we don't lose it - majf = self._sharex.xaxis.get_major_formatter() - minf = self._sharex.xaxis.get_minor_formatter() - majl = self._sharex.xaxis.get_major_locator() - minl = self._sharex.xaxis.get_minor_locator() - - # This overwrites the current formatter/locator - self.xaxis._set_scale(self._sharex.xaxis.get_scale()) - - # Reset the formatter/locator - self.xaxis.set_major_formatter(majf) - self.xaxis.set_minor_formatter(minf) - self.xaxis.set_major_locator(majl) - self.xaxis.set_minor_locator(minl) + self.xaxis._scale = mscale.scale_factory( + self._sharex.xaxis.get_scale(), self.xaxis) else: self.xaxis._set_scale('linear') try: @@ -1007,21 +994,8 @@ def cla(self): self.yaxis.minor = self._sharey.yaxis.minor y0, y1 = self._sharey.get_ylim() self.set_ylim(y0, y1, emit=False, auto=None) - - # Save the current formatter/locator so we don't lose it - majf = self._sharey.yaxis.get_major_formatter() - minf = self._sharey.yaxis.get_minor_formatter() - majl = self._sharey.yaxis.get_major_locator() - minl = self._sharey.yaxis.get_minor_locator() - - # This overwrites the current formatter/locator - self.yaxis._set_scale(self._sharey.yaxis.get_scale()) - - # Reset the formatter/locator - self.yaxis.set_major_formatter(majf) - self.yaxis.set_minor_formatter(minf) - self.yaxis.set_major_locator(majl) - self.yaxis.set_minor_locator(minl) + self.yaxis._scale = mscale.scale_factory( + self._sharey.yaxis.get_scale(), self.yaxis) else: self.yaxis._set_scale('linear') try: diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 3cd6e34c73c1..72c0d7f2f6fb 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -41,6 +41,7 @@ from contextlib import contextmanager import importlib import io +import itertools import os import sys import time @@ -100,6 +101,12 @@ } +# Used to ensure that caching based on renderer id() is unique without being as +# expensive as a real UUID. 0 is used for renderers that don't derive from +# here, so start at 1. +_unique_renderer_id = itertools.count(1) + + def register_backend(format, backend, description=None): """ Register a backend for saving to a given file format. @@ -212,6 +219,10 @@ class RendererBase(object): """ def __init__(self): + # A lightweight id for unique-ification purposes. Along with id(self), + # the combination should be unique enough to use as part of a cache key. + self._uid = next(_unique_renderer_id) + self._texmanager = None self._text2path = textpath.TextToPath() diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 223e088d71ba..35df7b6a3617 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -84,25 +84,18 @@ class RendererAgg(RendererBase): lock = threading.RLock() def __init__(self, width, height, dpi): - if __debug__: verbose.report('RendererAgg.__init__', 'debug-annoying') RendererBase.__init__(self) self.dpi = dpi self.width = width self.height = height - if __debug__: verbose.report('RendererAgg.__init__ width=%s, height=%s'%(width, height), 'debug-annoying') self._renderer = _RendererAgg(int(width), int(height), dpi, debug=False) self._filter_renderers = [] - if __debug__: verbose.report('RendererAgg.__init__ _RendererAgg done', - 'debug-annoying') - self._update_methods() self.mathtext_parser = MathTextParser('Agg') self.bbox = Bbox.from_bounds(0, 0, self.width, self.height) - if __debug__: verbose.report('RendererAgg.__init__ done', - 'debug-annoying') def __getstate__(self): # We only want to preserve the init keywords of the Renderer. @@ -178,8 +171,6 @@ def draw_mathtext(self, gc, x, y, s, prop, angle): """ Draw the math text using matplotlib.mathtext """ - if __debug__: verbose.report('RendererAgg.draw_mathtext', - 'debug-annoying') ox, oy, width, height, descent, font_image, used_characters = \ self.mathtext_parser.parse(s, self.dpi, prop) @@ -193,8 +184,6 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): """ Render the text """ - if __debug__: verbose.report('RendererAgg.draw_text', 'debug-annoying') - if ismath: return self.draw_mathtext(gc, x, y, s, prop, angle) @@ -279,9 +268,6 @@ def _get_agg_font(self, prop): """ Get the font for text instance t, cacheing for efficiency """ - if __debug__: verbose.report('RendererAgg._get_agg_font', - 'debug-annoying') - fname = findfont(prop) font = get_font( fname, @@ -298,23 +284,15 @@ def points_to_pixels(self, points): convert point measures to pixes using dpi and the pixels per inch of the display """ - if __debug__: verbose.report('RendererAgg.points_to_pixels', - 'debug-annoying') return points*self.dpi/72.0 def tostring_rgb(self): - if __debug__: verbose.report('RendererAgg.tostring_rgb', - 'debug-annoying') return self._renderer.tostring_rgb() def tostring_argb(self): - if __debug__: verbose.report('RendererAgg.tostring_argb', - 'debug-annoying') return self._renderer.tostring_argb() def buffer_rgba(self): - if __debug__: verbose.report('RendererAgg.buffer_rgba', - 'debug-annoying') return self._renderer.buffer_rgba() def clear(self): @@ -423,10 +401,6 @@ def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance """ - if __debug__: verbose.report('backend_agg.new_figure_manager', - 'debug-annoying') - - FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) return new_figure_manager_given_figure(num, thisFig) @@ -465,8 +439,6 @@ def draw(self): """ Draw the figure using the renderer """ - if __debug__: verbose.report('FigureCanvasAgg.draw', 'debug-annoying') - self.renderer = self.get_renderer(cleared=True) # acquire a lock on the shared font cache RendererAgg.lock.acquire() @@ -500,8 +472,6 @@ def tostring_rgb(self): ------- bytes ''' - if __debug__: verbose.report('FigureCanvasAgg.tostring_rgb', - 'debug-annoying') return self.renderer.tostring_rgb() def tostring_argb(self): @@ -515,8 +485,6 @@ def tostring_argb(self): bytes ''' - if __debug__: verbose.report('FigureCanvasAgg.tostring_argb', - 'debug-annoying') return self.renderer.tostring_argb() def buffer_rgba(self): @@ -529,8 +497,6 @@ def buffer_rgba(self): ------- bytes ''' - if __debug__: verbose.report('FigureCanvasAgg.buffer_rgba', - 'debug-annoying') return self.renderer.buffer_rgba() def print_raw(self, filename_or_obj, *args, **kwargs): diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index 7396452fa0e3..c41c74711214 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -27,8 +27,6 @@ import numpy as np -def _fn_name(): return sys._getframe(1).f_code.co_name - try: import cairocffi as cairo except ImportError: @@ -58,9 +56,6 @@ def _fn_name(): return sys._getframe(1).f_code.co_name from matplotlib.transforms import Bbox, Affine2D from matplotlib.font_manager import ttfFontProperty -_debug = False -#_debug = True - # Image::color_conv(format) for draw_image() if sys.byteorder == 'little': BYTE_FORMAT = 0 # BGRA @@ -114,7 +109,6 @@ class RendererCairo(RendererBase): def __init__(self, dpi): """ """ - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) self.dpi = dpi self.gc = GraphicsContextCairo (renderer=self) self.text_ctx = cairo.Context ( @@ -227,8 +221,6 @@ def draw_markers(self, gc, marker_path, marker_trans, path, transform, rgbFace=N def draw_image(self, gc, x, y, im): # bbox - not currently used - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) - if sys.byteorder == 'little': im = im[:, :, (2, 1, 0, 3)] else: @@ -266,8 +258,6 @@ def draw_image(self, gc, x, y, im): def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): # Note: x,y are device/display coords, not user-coords, unlike other # draw_* methods - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) - if ismath: self._draw_mathtext(gc, x, y, s, prop, angle) @@ -297,8 +287,6 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): ctx.restore() def _draw_mathtext(self, gc, x, y, s, prop, angle): - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) - ctx = gc.ctx width, height, descent, glyphs, rects = self.mathtext_parser.parse( s, self.dpi, prop) @@ -335,19 +323,16 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle): def flipy(self): - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) return True #return False # tried - all draw objects ok except text (and images?) # which comes out mirrored! def get_canvas_width_height(self): - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) return self.width, self.height def get_text_width_height_descent(self, s, prop, ismath): - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) if ismath: width, height, descent, fonts, used_characters = self.mathtext_parser.parse( s, self.dpi, prop) @@ -376,7 +361,6 @@ def get_text_width_height_descent(self, s, prop, ismath): def new_gc(self): - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) self.gc.ctx.save() self.gc._alpha = 1.0 self.gc._forced_alpha = False # if True, _alpha overrides A from RGBA @@ -384,7 +368,6 @@ def new_gc(self): def points_to_pixels(self, points): - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) return points/72.0 * self.dpi @@ -488,7 +471,6 @@ def new_figure_manager(num, *args, **kwargs): # called by backends/__init__.py """ Create a new figure manager instance """ - if _debug: print('%s()' % (_fn_name())) FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) return new_figure_manager_given_figure(num, thisFig) diff --git a/lib/matplotlib/backends/backend_gdk.py b/lib/matplotlib/backends/backend_gdk.py index 36583cf44402..8e9d424a8075 100644 --- a/lib/matplotlib/backends/backend_gdk.py +++ b/lib/matplotlib/backends/backend_gdk.py @@ -7,7 +7,6 @@ import os import sys import warnings -def fn_name(): return sys._getframe(1).f_code.co_name import gobject import gtk; gdk = gtk.gdk @@ -24,8 +23,8 @@ def fn_name(): return sys._getframe(1).f_code.co_name import matplotlib from matplotlib import rcParams from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \ - FigureManagerBase, FigureCanvasBase +from matplotlib.backend_bases import ( + RendererBase, GraphicsContextBase, FigureManagerBase, FigureCanvasBase) from matplotlib.cbook import restrict_dict, warn_deprecated from matplotlib.figure import Figure from matplotlib.mathtext import MathTextParser @@ -33,7 +32,6 @@ def fn_name(): return sys._getframe(1).f_code.co_name from matplotlib.backends._backend_gdk import pixbuf_get_pixels_array backend_version = "%d.%d.%d" % gtk.pygtk_version -_debug = False # Image formats that this backend supports - for FileChooser and print_figure() IMAGE_FORMAT = sorted(['bmp', 'eps', 'jpg', 'png', 'ps', 'svg']) # 'raw', 'rgb' diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index f9a2fa47edd0..a5dec05faeaa 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -4,7 +4,6 @@ import six import os, sys, warnings -def fn_name(): return sys._getframe(1).f_code.co_name if six.PY3: warnings.warn( @@ -44,9 +43,6 @@ def fn_name(): return sys._getframe(1).f_code.co_name backend_version = "%d.%d.%d" % gtk.pygtk_version -_debug = False -#_debug = True - # the true dots per inch on the screen; should be display dependent # see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi PIXELS_PER_INCH = 96 @@ -225,7 +221,6 @@ def __init__(self, figure): "See Matplotlib usage FAQ for" " more info on backends.", alternative="GTKAgg") - if _debug: print('FigureCanvasGTK.%s' % fn_name()) FigureCanvasBase.__init__(self, figure) gtk.DrawingArea.__init__(self) @@ -261,7 +256,6 @@ def destroy(self): gobject.source_remove(self._idle_draw_id) def scroll_event(self, widget, event): - if _debug: print('FigureCanvasGTK.%s' % fn_name()) x = event.x # flipy so y=0 is bottom of canvas y = self.allocation.height - event.y @@ -273,7 +267,6 @@ def scroll_event(self, widget, event): return False # finish event propagation? def button_press_event(self, widget, event): - if _debug: print('FigureCanvasGTK.%s' % fn_name()) x = event.x # flipy so y=0 is bottom of canvas y = self.allocation.height - event.y @@ -297,7 +290,6 @@ def button_press_event(self, widget, event): return False # finish event propagation? def button_release_event(self, widget, event): - if _debug: print('FigureCanvasGTK.%s' % fn_name()) x = event.x # flipy so y=0 is bottom of canvas y = self.allocation.height - event.y @@ -305,21 +297,16 @@ def button_release_event(self, widget, event): return False # finish event propagation? def key_press_event(self, widget, event): - if _debug: print('FigureCanvasGTK.%s' % fn_name()) key = self._get_key(event) - if _debug: print("hit", key) FigureCanvasBase.key_press_event(self, key, guiEvent=event) return True # stop event propagation def key_release_event(self, widget, event): - if _debug: print('FigureCanvasGTK.%s' % fn_name()) key = self._get_key(event) - if _debug: print("release", key) FigureCanvasBase.key_release_event(self, key, guiEvent=event) return True # stop event propagation def motion_notify_event(self, widget, event): - if _debug: print('FigureCanvasGTK.%s' % fn_name()) if event.is_hint: x, y, state = event.window.get_pointer() else: @@ -355,7 +342,6 @@ def _get_key(self, event): return key def configure_event(self, widget, event): - if _debug: print('FigureCanvasGTK.%s' % fn_name()) if widget.window is None: return w, h = event.width, event.height @@ -408,8 +394,6 @@ def _pixmap_prepare(self, width, height): Make sure _._pixmap is at least width, height, create new pixmap if necessary """ - if _debug: print('FigureCanvasGTK.%s' % fn_name()) - create_pixmap = False if width > self._pixmap_width: # increase the pixmap in 10%+ (rather than 1 pixel) steps @@ -438,8 +422,6 @@ def _render_figure(self, pixmap, width, height): def expose_event(self, widget, event): """Expose_event for all GTK backends. Should not be overridden. """ - if _debug: print('FigureCanvasGTK.%s' % fn_name()) - if GTK_WIDGET_DRAWABLE(self): if self._need_redraw: x, y, w, h = self.allocation @@ -556,7 +538,6 @@ class FigureManagerGTK(FigureManagerBase): """ def __init__(self, canvas, num): - if _debug: print('FigureManagerGTK.%s' % fn_name()) FigureManagerBase.__init__(self, canvas, num) self.window = gtk.Window() @@ -610,7 +591,6 @@ def notify_axes_change(fig): self.canvas.grab_focus() def destroy(self, *args): - if _debug: print('FigureManagerGTK.%s' % fn_name()) if hasattr(self, 'toolbar') and self.toolbar is not None: self.toolbar.destroy() if hasattr(self, 'vbox'): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 6a1f74a30c65..c084f6d6dccc 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -4,7 +4,6 @@ import six import os, sys -def fn_name(): return sys._getframe(1).f_code.co_name try: import gi @@ -28,23 +27,21 @@ def fn_name(): return sys._getframe(1).f_code.co_name import matplotlib from matplotlib._pylab_helpers import Gcf -from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \ - FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors, TimerBase -from matplotlib.backend_bases import (ShowBase, ToolContainerBase, - StatusbarBase) +from matplotlib.backend_bases import ( + FigureCanvasBase, FigureManagerBase, GraphicsContextBase, + NavigationToolbar2, RendererBase, TimerBase, cursors) +from matplotlib.backend_bases import ( + ShowBase, ToolContainerBase, StatusbarBase) from matplotlib.backend_managers import ToolManager -from matplotlib import backend_tools - from matplotlib.cbook import is_writable_file_like from matplotlib.figure import Figure from matplotlib.widgets import SubplotTool -from matplotlib import cbook, colors as mcolors, lines, verbose, rcParams - -backend_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version()) +from matplotlib import ( + backend_tools, cbook, colors as mcolors, lines, verbose, rcParams) -_debug = False -#_debug = True +backend_version = "%s.%s.%s" % ( + Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version()) # the true dots per inch on the screen; should be display dependent # see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi @@ -119,7 +116,7 @@ def _on_timer(self): self._timer = None return False -class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): +class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase): keyvald = {65507 : 'control', 65505 : 'shift', 65513 : 'alt', @@ -185,7 +182,6 @@ class FigureCanvasGTK3 (Gtk.DrawingArea, FigureCanvasBase): Gdk.EventMask.SCROLL_MASK) def __init__(self, figure): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) FigureCanvasBase.__init__(self, figure) GObject.GObject.__init__(self) @@ -219,7 +215,6 @@ def destroy(self): GLib.source_remove(self._idle_draw_id) def scroll_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) x = event.x # flipy so y=0 is bottom of canvas y = self.get_allocation().height - event.y @@ -231,7 +226,6 @@ def scroll_event(self, widget, event): return False # finish event propagation? def button_press_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) x = event.x # flipy so y=0 is bottom of canvas y = self.get_allocation().height - event.y @@ -239,7 +233,6 @@ def button_press_event(self, widget, event): return False # finish event propagation? def button_release_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) x = event.x # flipy so y=0 is bottom of canvas y = self.get_allocation().height - event.y @@ -247,21 +240,16 @@ def button_release_event(self, widget, event): return False # finish event propagation? def key_press_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) key = self._get_key(event) - if _debug: print("hit", key) FigureCanvasBase.key_press_event(self, key, guiEvent=event) return True # stop event propagation def key_release_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) key = self._get_key(event) - if _debug: print("release", key) FigureCanvasBase.key_release_event(self, key, guiEvent=event) return True # stop event propagation def motion_notify_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) if event.is_hint: t, x, y, state = event.window.get_pointer() else: @@ -279,9 +267,6 @@ def enter_notify_event(self, widget, event): FigureCanvasBase.enter_notify_event(self, event) def size_allocate(self, widget, allocation): - if _debug: - print("FigureCanvasGTK3.%s" % fn_name()) - print("size_allocate (%d x %d)" % (allocation.width, allocation.height)) dpival = self.figure.dpi winch = allocation.width / dpival hinch = allocation.height / dpival @@ -309,7 +294,6 @@ def _get_key(self, event): return key def configure_event(self, widget, event): - if _debug: print('FigureCanvasGTK3.%s' % fn_name()) if widget.get_property("window") is None: return w, h = event.width, event.height @@ -395,7 +379,6 @@ class FigureManagerGTK3(FigureManagerBase): """ def __init__(self, canvas, num): - if _debug: print('FigureManagerGTK3.%s' % fn_name()) FigureManagerBase.__init__(self, canvas, num) self.window = Gtk.Window() @@ -468,16 +451,15 @@ def notify_axes_change(fig): self.canvas.grab_focus() def destroy(self, *args): - if _debug: print('FigureManagerGTK3.%s' % fn_name()) self.vbox.destroy() self.window.destroy() self.canvas.destroy() if self.toolbar: self.toolbar.destroy() - if Gcf.get_num_fig_managers()==0 and \ - not matplotlib.is_interactive() and \ - Gtk.main_level() >= 1: + if (Gcf.get_num_fig_managers() == 0 and + not matplotlib.is_interactive() and + Gtk.main_level() >= 1): Gtk.main_quit() def show(self): @@ -497,7 +479,7 @@ def _get_toolbar(self): # must be inited after the window, drawingArea and figure # attrs are set if rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2GTK3 (self.canvas, self.window) + toolbar = NavigationToolbar2GTK3(self.canvas, self.window) elif rcParams['toolbar'] == 'toolmanager': toolbar = ToolbarGTK3(self.toolmanager) else: @@ -941,7 +923,8 @@ def trigger(self, sender, event, data=None): icon_filename = 'matplotlib.png' else: icon_filename = 'matplotlib.svg' -window_icon = os.path.join(matplotlib.rcParams['datapath'], 'images', icon_filename) +window_icon = os.path.join( + matplotlib.rcParams['datapath'], 'images', icon_filename) def error_msg_gtk(msg, parent=None): @@ -951,7 +934,7 @@ def error_msg_gtk(msg, parent=None): parent = None if not isinstance(msg, six.string_types): - msg = ','.join(map(str,msg)) + msg = ','.join(map(str, msg)) dialog = Gtk.MessageDialog( parent = parent, diff --git a/lib/matplotlib/backends/backend_gtkcairo.py b/lib/matplotlib/backends/backend_gtkcairo.py index 93bb69857a1f..1440b85044a9 100644 --- a/lib/matplotlib/backends/backend_gtkcairo.py +++ b/lib/matplotlib/backends/backend_gtkcairo.py @@ -8,25 +8,20 @@ import six import gtk -if gtk.pygtk_version < (2,7,0): +if gtk.pygtk_version < (2, 7, 0): import cairo.gtk from matplotlib.backends import backend_cairo from matplotlib.backends.backend_gtk import * -backend_version = 'PyGTK(%d.%d.%d) ' % gtk.pygtk_version + \ - 'Pycairo(%s)' % backend_cairo.backend_version - - -_debug = False -#_debug = True +backend_version = ('PyGTK(%d.%d.%d) ' % gtk.pygtk_version + + 'Pycairo(%s)' % backend_cairo.backend_version) def new_figure_manager(num, *args, **kwargs): """ Create a new figure manager instance """ - if _debug: print('backend_gtkcairo.%s()' % fn_name()) FigureClass = kwargs.pop('FigureClass', Figure) thisFig = FigureClass(*args, **kwargs) return new_figure_manager_given_figure(num, thisFig) @@ -55,8 +50,7 @@ class FigureCanvasGTKCairo(backend_cairo.FigureCanvasCairo, FigureCanvasGTK): def _renderer_init(self): """Override to use cairo (rather than GDK) renderer""" - if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name())) - self._renderer = RendererGTKCairo (self.figure.dpi) + self._renderer = RendererGTKCairo(self.figure.dpi) class FigureManagerGTKCairo(FigureManagerGTK): diff --git a/lib/matplotlib/backends/backend_mixed.py b/lib/matplotlib/backends/backend_mixed.py index 40d7fd64398c..a93ef062f279 100644 --- a/lib/matplotlib/backends/backend_mixed.py +++ b/lib/matplotlib/backends/backend_mixed.py @@ -5,6 +5,7 @@ import six +import matplotlib.backend_bases from matplotlib.backends.backend_agg import RendererAgg from matplotlib.tight_bbox import process_figure_for_rasterizing @@ -49,6 +50,9 @@ def __init__(self, figure, width, height, dpi, vector_renderer, if raster_renderer_class is None: raster_renderer_class = RendererAgg + # See matplotlib.backend_bases.RendererBase._uid. + self._uid = next(matplotlib.backend_bases._unique_renderer_id) + self._raster_renderer_class = raster_renderer_class self._width = width self._height = height diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index ffad5187c9c2..0e4e2011841c 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -9,7 +9,6 @@ from six.moves import StringIO import glob, os, shutil, sys, time, datetime -def _fn_name(): return sys._getframe(1).f_code.co_name import io from tempfile import mkstemp diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py index a10dd19a1788..c90a36c2a648 100644 --- a/lib/matplotlib/backends/backend_qt4.py +++ b/lib/matplotlib/backends/backend_qt4.py @@ -25,12 +25,11 @@ from .qt_compat import QtCore, QtWidgets, _getSaveFileName, __version__ -from .backend_qt5 import (backend_version, SPECIAL_KEYS, SUPER, ALT, CTRL, - SHIFT, MODIFIER_KEYS, fn_name, cursord, - draw_if_interactive, _create_qApp, show, TimerQT, - MainWindow, FigureManagerQT, NavigationToolbar2QT, - SubplotToolQt, error_msg_qt, exception_handler) - +from .backend_qt5 import ( + backend_version, SPECIAL_KEYS, SUPER, ALT, CTRL, SHIFT, MODIFIER_KEYS, + cursord, draw_if_interactive, _create_qApp, show, TimerQT, MainWindow, + FigureManagerQT, NavigationToolbar2QT, SubplotToolQt, error_msg_qt, + exception_handler) from .backend_qt5 import FigureCanvasQT as FigureCanvasQT5 DEBUG = False diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 9c3518ffc9e7..291c0caab68f 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -90,11 +90,6 @@ QtCore.Qt.Key_Meta) -def fn_name(): - return sys._getframe(1).f_code.co_name - -DEBUG = False - cursord = { cursors.MOVE: QtCore.Qt.SizeAllCursor, cursors.HAND: QtCore.Qt.PointingHandCursor, @@ -123,8 +118,6 @@ def _create_qApp(): global qApp if qApp is None: - if DEBUG: - print("Starting up QApplication") app = QtWidgets.QApplication.instance() if app is None: # check for DISPLAY env variable on X11 build of Qt @@ -233,8 +226,6 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase): } def __init__(self, figure): - if DEBUG: - print('FigureCanvasQt qt5: ', figure) _create_qApp() # NB: Using super for this call to avoid a TypeError: @@ -292,8 +283,6 @@ def mousePressEvent(self, event): if button is not None: FigureCanvasBase.button_press_event(self, x, y, button, guiEvent=event) - if DEBUG: - print('button pressed:', event.button()) def mouseDoubleClickEvent(self, event): x, y = self.mouseEventCoords(event.pos()) @@ -302,13 +291,10 @@ def mouseDoubleClickEvent(self, event): FigureCanvasBase.button_press_event(self, x, y, button, dblclick=True, guiEvent=event) - if DEBUG: - print('button doubleclicked:', event.button()) def mouseMoveEvent(self, event): x, y = self.mouseEventCoords(event) FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event) - # if DEBUG: print('mouse move') def mouseReleaseEvent(self, event): x, y = self.mouseEventCoords(event) @@ -316,8 +302,6 @@ def mouseReleaseEvent(self, event): if button is not None: FigureCanvasBase.button_release_event(self, x, y, button, guiEvent=event) - if DEBUG: - print('button released') def wheelEvent(self, event): x, y = self.mouseEventCoords(event) @@ -326,28 +310,18 @@ def wheelEvent(self, event): steps = event.angleDelta().y() / 120 else: steps = event.pixelDelta().y() - - if steps != 0: + if steps: FigureCanvasBase.scroll_event(self, x, y, steps, guiEvent=event) - if DEBUG: - print('scroll event: delta = %i, ' - 'steps = %i ' % (event.delta(), steps)) def keyPressEvent(self, event): key = self._get_key(event) - if key is None: - return - FigureCanvasBase.key_press_event(self, key, guiEvent=event) - if DEBUG: - print('key press', key) + if key is not None: + FigureCanvasBase.key_press_event(self, key, guiEvent=event) def keyReleaseEvent(self, event): key = self._get_key(event) - if key is None: - return - FigureCanvasBase.key_release_event(self, key, guiEvent=event) - if DEBUG: - print('key release', key) + if key is not None: + FigureCanvasBase.key_release_event(self, key, guiEvent=event) @property def keyAutoRepeat(self): @@ -363,9 +337,6 @@ def keyAutoRepeat(self, val): def resizeEvent(self, event): w = event.size().width() * self._dpi_ratio h = event.size().height() * self._dpi_ratio - if DEBUG: - print('resize (%d x %d)' % (w, h)) - print("FigureCanvasQt.resizeEvent(%d, %d)" % (w, h)) dpival = self.figure.dpi winch = w / dpival hinch = h / dpival @@ -478,8 +449,6 @@ class FigureManagerQT(FigureManagerBase): """ def __init__(self, canvas, num): - if DEBUG: - print('FigureManagerQT.%s' % fn_name()) FigureManagerBase.__init__(self, canvas, num) self.canvas = canvas self.window = MainWindow() @@ -578,11 +547,8 @@ def destroy(self, *args): return self.window._destroying = True self.window.destroyed.connect(self._widgetclosed) - if self.toolbar: self.toolbar.destroy() - if DEBUG: - print("destroy figure manager") self.window.close() def get_window_title(self): @@ -712,8 +678,6 @@ def set_message(self, s): self.locLabel.setText(s) def set_cursor(self, cursor): - if DEBUG: - print('Set cursor', cursor) self.canvas.setCursor(cursord[cursor]) def draw_rubberband(self, event, x0, y0, x1, y1): @@ -724,7 +688,7 @@ def draw_rubberband(self, event, x0, y0, x1, y1): w = abs(x1 - x0) h = abs(y1 - y0) - rect = [int(val)for val in (min(x0, x1), min(y0, y1), w, h)] + rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] self.canvas.drawRectangle(rect) def remove_rubberband(self): diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index af0ed62f9d81..371126d3d660 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -719,7 +719,6 @@ def __init__(self, canvas, window): self.canvas = canvas self.window = window self._idle = True - #Tk.Frame.__init__(self, master=self.canvas._tkcanvas) NavigationToolbar2.__init__(self, canvas) def destroy(self, *args): @@ -731,11 +730,10 @@ def set_message(self, s): def draw_rubberband(self, event, x0, y0, x1, y1): height = self.canvas.figure.bbox.height - y0 = height-y0 - y1 = height-y1 - try: self.lastrect - except AttributeError: pass - else: self.canvas._tkcanvas.delete(self.lastrect) + 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) #self.canvas.draw() @@ -751,7 +749,8 @@ def set_cursor(self, cursor): self.window.configure(cursor=cursord[cursor]) def _Button(self, text, file, command, extension='.gif'): - img_file = os.path.join(rcParams['datapath'], 'images', file + extension) + img_file = os.path.join( + rcParams['datapath'], '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) @@ -761,10 +760,11 @@ def _Button(self, text, file, command, extension='.gif'): def _Spacer(self): # Buttons are 30px high, so make this 26px tall with padding to center it - s = Tk.Frame(master=self, height=26, relief=Tk.RIDGE, pady=2, bg="DarkGray") + 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 @@ -776,11 +776,11 @@ def _init_toolbar(self): for text, tooltip_text, image_file, callback in self.toolitems: if text is None: - # Add a spacer -- we don't need to use the return value for anything + # Add a spacer; return value is unused. self._Spacer() else: button = self._Button(text=text, file=image_file, - command=getattr(self, callback)) + command=getattr(self, callback)) if tooltip_text is not None: ToolTip.createToolTip(button, tooltip_text) @@ -789,7 +789,6 @@ def _init_toolbar(self): 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.Tk() @@ -846,17 +845,11 @@ def save_figure(self, *args): def set_active(self, ind): self._ind = ind - self._active = [ self._axes[i] for i in self._ind ] + self._active = [self._axes[i] for i in self._ind] def update(self): _focus = windowing.FocusManager() self._axes = self.canvas.figure.axes - naxes = len(self._axes) - #if not hasattr(self, "omenu"): - # self.set_active(range(naxes)) - # self.omenu = AxisMenu(master=self, naxes=naxes) - #else: - # self.omenu.adjust(naxes) NavigationToolbar2.update(self) @@ -900,8 +893,7 @@ def showtip(self, text): except Tk.TclError: pass label = Tk.Label(tw, text=self.text, justify=Tk.LEFT, - background="#ffffe0", relief=Tk.SOLID, borderwidth=1, - ) + background="#ffffe0", relief=Tk.SOLID, borderwidth=1) label.pack(ipadx=1) def hidetip(self): @@ -919,20 +911,13 @@ def draw_rubberband(self, x0, y0, x1, y1): height = self.figure.canvas.figure.bbox.height y0 = height - y0 y1 = height - y1 - try: - self.lastrect - except AttributeError: - pass - else: + if hasattr(self, "lastrect"): self.figure.canvas._tkcanvas.delete(self.lastrect) - self.lastrect = self.figure.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1) + self.lastrect = self.figure.canvas._tkcanvas.create_rectangle( + x0, y0, x1, y1) def remove_rubberband(self): - try: - self.lastrect - except AttributeError: - pass - else: + if hasattr(self, "lastrect"): self.figure.canvas._tkcanvas.delete(self.lastrect) del self.lastrect @@ -954,8 +939,8 @@ def __init__(self, toolmanager, window): self.pack(side=Tk.TOP, fill=Tk.X) self._groups = {} - def add_toolitem(self, name, group, position, image_file, description, - toggle): + 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: diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index a080eb59247c..6e0d34830d87 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1117,9 +1117,10 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, parent_anchor = kw.pop('panchor', loc_settings['panchor']) pad = kw.pop('pad', loc_settings['pad']) - # turn parents into a list if it is not already - if not isinstance(parents, (list, tuple)): - parents = [parents] + # turn parents into a list if it is not already. We do this w/ np + # because `plt.subplots` can return an ndarray and is natural to + # pass to `colorbar`. + parents = np.atleast_1d(parents).ravel() fig = parents[0].get_figure() if not all(fig is ax.get_figure() for ax in parents): @@ -1164,24 +1165,36 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, return cax, kw +# helper functions for row,col to index. +def index2rowcolunm(index, ncols): + col = index%ncols + 1 + row = int(np.floor(index/ncols)+1) + return row, col + + +def rowcolunm2index(row, col, ncols): + index = col-1+(row-1)*ncols + return index + + @docstring.Substitution(make_axes_kw_doc) -def make_axes_gridspec(parent, **kw): +def make_axes_gridspec(parents, **kw): ''' - Resize and reposition a parent axes, and return a child axes + Resize and reposition a list of parent axes, and return a child axes suitable for a colorbar. This function is similar to - make_axes. Prmary differences are + make_axes. Primary differences are * *make_axes_gridspec* only handles the *orientation* keyword and cannot handle the "location" keyword. - * *make_axes_gridspec* should only be used with a subplot parent. + * *make_axes_gridspec* should only be used with subplot parents. * *make_axes* creates an instance of Axes. *make_axes_gridspec* creates an instance of Subplot. * *make_axes* updates the position of the - parent. *make_axes_gridspec* replaces the grid_spec attribute - of the parent with a new one. + parents. *make_axes_gridspec* replaces the grid_spec attribute + of the parents with new ones. While this function is meant to be compatible with *make_axes*, there could be some minor differences. @@ -1212,17 +1225,52 @@ def make_axes_gridspec(parent, **kw): pad_s = (1. - shrink) * 0.5 wh_ratios = [pad_s, shrink, pad_s] + # make parents a 1-d ndarray if its not already... + parents = np.atleast_1d(parents).ravel() + # For the subplotspec that these axes belong to, loop through and get + # the maximum and minimum index into the subplotspec. The result is the + # size the new gridspec that we will create must be. + gsp0 = parents[0].get_subplotspec().get_gridspec() + minind = 10000 + maxind = -10000 + for parent in parents: + gsp = parent.get_subplotspec().get_gridspec() + if gsp == gsp0: + ss = list(parent.get_subplotspec().get_geometry()) + if ss[3] is None: + ss[3] = ss[2] + if ss[2] < minind: + minind = ss[2] + if ss[3] > maxind: + maxind = ss[3] + else: + raise NotImplementedError('List of axes passed to colorbar ' + 'must be from the same gridspec or call to plt.subplots') + # map the extent of the gridspec from indices to rows and columns. + # We need this below to assign the parents into the new gridspec. + ncols0 = gsp0.get_geometry()[1] + minrow, mincol = index2rowcolunm(minind, ncols0) + maxrow, maxcol = index2rowcolunm(maxind, ncols0) + nrows = maxrow-minrow+1 + ncols = maxcol-mincol+1 + + # this is subplot spec the region that we need to resize and add + # a colorbar to. + subspec = gridspec.SubplotSpec(gsp0, minind, maxind) + gs_from_subplotspec = gridspec.GridSpecFromSubplotSpec if orientation == 'vertical': pad = kw.pop('pad', 0.05) wh_space = 2 * pad / (1 - pad) + # split the subplotspec containing parents into two, with one only + # `fraction` wide for the colorbar, and the other with some padding. gs = gs_from_subplotspec(1, 2, - subplot_spec=parent.get_subplotspec(), + subplot_spec=subspec, wspace=wh_space, width_ratios=[x1 - pad, fraction] ) - + # center the colorbar vertically. gs2 = gs_from_subplotspec(3, 1, subplot_spec=gs[1], hspace=0., @@ -1236,7 +1284,7 @@ def make_axes_gridspec(parent, **kw): wh_space = 2 * pad / (1 - pad) gs = gs_from_subplotspec(2, 1, - subplot_spec=parent.get_subplotspec(), + subplot_spec=subspec, hspace=wh_space, height_ratios=[x1 - pad, fraction] ) @@ -1251,12 +1299,40 @@ def make_axes_gridspec(parent, **kw): anchor = (0.5, 1.0) panchor = (0.5, 0.0) - parent.set_subplotspec(gs[0]) - parent.update_params() - parent.set_position(parent.figbox) - parent.set_anchor(panchor) + # we need to repackage the parent axes into gs. + # We need to know where they are in the new gs. + # the new gs has nrows by ncols. + gsnew = gs_from_subplotspec(nrows, ncols, subplot_spec=gs[0]) + + for parent in parents: + geo = parent.get_subplotspec().get_geometry() + ncol0 = geo[1] + + # remap the old min gridspec index (geo[2]) into a new + # index. + oldrow, oldcol = index2rowcolunm(geo[2], ncol0) + newrow = oldrow - minrow+1 + newcol = oldcol - mincol+1 + newminind = rowcolunm2index(newrow, newcol, ncols) + + # remap the old max gridspec index (geo[3]) into a new + # index. + if geo[3] is None: + newmaxind = newminind + else: + oldrow, oldcol = index2rowcolunm(geo[3], ncol0) + newrow = oldrow - minrow+1 + newcol = oldcol - mincol+1 + newmaxind = rowcolunm2index(newrow, newcol, ncols) + + # change the subplotspec for this parent. + parent.set_subplotspec( + gridspec.SubplotSpec(gsnew, newminind, newmaxind)) + parent.update_params() + parent.set_position(parent.figbox) + parent.set_anchor(panchor) - fig = parent.get_figure() + fig = parents[0].get_figure() cax = fig.add_subplot(gs2[1]) cax.set_aspect(aspect, anchor=anchor, adjustable='box') return cax, kw diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 82d9fb023312..5d3fc1126c0d 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -1254,11 +1254,11 @@ def _process_colors(self): i0, i1 = 0, len(self.levels) if self.filled: i1 -= 1 - # Out of range indices for over and under: - if self.extend in ('both', 'min'): - i0 = -1 - if self.extend in ('both', 'max'): - i1 += 1 + # Out of range indices for over and under: + if self.extend in ('both', 'min'): + i0 -= 1 + if self.extend in ('both', 'max'): + i1 += 1 self.cvalues = list(range(i0, i1)) self.set_norm(colors.NoNorm()) else: diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 7034ea64c0d0..1cb952b3d322 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1794,12 +1794,12 @@ def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw): """ if ax is None: ax = self.gca() + ax = np.atleast_1d(ax).ravel() # Store the value of gca so that we can set it back later on. current_ax = self.gca() - if cax is None: - if use_gridspec and isinstance(ax, SubplotBase): + if use_gridspec and isinstance(ax[0], SubplotBase): cax, kw = cbar.make_axes_gridspec(ax, **kw) else: cax, kw = cbar.make_axes(ax, **kw) diff --git a/lib/matplotlib/mlab.py b/lib/matplotlib/mlab.py index ae9143c98f5a..971770790c40 100644 --- a/lib/matplotlib/mlab.py +++ b/lib/matplotlib/mlab.py @@ -727,12 +727,12 @@ def _spectral_helper(x, y=None, NFFT=None, Fs=None, detrend_func=None, elif mode == 'psd': result = np.conj(result) * result elif mode == 'magnitude': - result = np.abs(result) + result = np.abs(result) / np.abs(windowVals).sum() elif mode == 'angle' or mode == 'phase': # we unwrap the phase later to handle the onesided vs. twosided case result = np.angle(result) elif mode == 'complex': - pass + result /= np.abs(windowVals).sum() if mode == 'psd': diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 10bc425b7734..36a9c8e30b93 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -3995,6 +3995,10 @@ def transmute(self, path, mutation_size, linewidth): class FancyArrowPatch(Patch): """ A fancy arrow patch. It draws an arrow using the :class:`ArrowStyle`. + + The head and tail positions are fixed at the specified start and end points + of the arrow, but the size and shape (in display coordinates) of the arrow + does not change when the axis is moved or zoomed. """ _edge_default = True diff --git a/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_freqs_dB.png b/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_freqs_dB.png index ebc50c676692..15d32d399208 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_freqs_dB.png and b/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_freqs_dB.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_freqs_linear.png b/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_freqs_linear.png index 14f832a7355b..9dec3eef4339 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_freqs_linear.png and b/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_freqs_linear.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_noise_dB.png b/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_noise_dB.png index a18d0b40f43b..a8ceb982774b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_noise_dB.png and b/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_noise_dB.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_noise_linear.png b/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_noise_linear.png index c2deb9e2414d..95a64798607b 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_noise_linear.png and b/lib/matplotlib/tests/baseline_images/test_axes/magnitude_spectrum_noise_linear.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png index 6de9c579758d..c4ec5f0beed4 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png and b/lib/matplotlib/tests/baseline_images/test_contour/contour_manual_colors_and_levels.png differ diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 3529ea8541db..d52fc3efdf66 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -178,6 +178,8 @@ def test_grayscale_alpha(): ax.set_yticks([]) +# This tests tends to hit a TeX cache lock on AppVeyor. +@pytest.mark.flaky(reruns=3) @needs_tex def test_missing_psfont(monkeypatch): """An error is raised if a TeX font lacks a Type-1 equivalent""" diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 8ead41d67d01..cd6e5feae14a 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -27,6 +27,8 @@ reason="This test needs a TeX installation") +# This tests tends to hit a TeX cache lock on AppVeyor. +@pytest.mark.flaky(reruns=3) @pytest.mark.parametrize('format, use_log, rcParams', [ ('ps', False, {}), needs_ghostscript(('ps', False, {'ps.usedistiller': 'ghostscript'})), diff --git a/lib/matplotlib/tests/test_basic.py b/lib/matplotlib/tests/test_basic.py index 236e0d9e7d8d..945e78322b3d 100644 --- a/lib/matplotlib/tests/test_basic.py +++ b/lib/matplotlib/tests/test_basic.py @@ -20,7 +20,8 @@ def test_override_builtins(): '__spec__', 'any', 'all', - 'sum' + 'sum', + 'divmod' } # We could use six.moves.builtins here, but that seems diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index 5e2211066f3c..eb2be0991ad6 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -168,21 +168,22 @@ def test_given_colors_levels_and_extends(): levels = [2, 4, 8, 10] for i, ax in enumerate(axes.flatten()): - plt.sca(ax) - filled = i % 2 == 0. extend = ['neither', 'min', 'max', 'both'][i // 2] if filled: - last_color = -1 if extend in ['min', 'max'] else None - plt.contourf(data, colors=colors[:last_color], levels=levels, - extend=extend) + # If filled, we have 3 colors with no extension, + # 4 colors with one extension, and 5 colors with both extensions + first_color = 1 if extend in ['max', 'neither'] else None + last_color = -1 if extend in ['min', 'neither'] else None + c = ax.contourf(data, colors=colors[first_color:last_color], + levels=levels, extend=extend) else: - last_level = -1 if extend == 'both' else None - plt.contour(data, colors=colors, levels=levels[:last_level], - extend=extend) + # If not filled, we have 4 levels and 4 colors + c = ax.contour(data, colors=colors[:-1], + levels=levels, extend=extend) - plt.colorbar() + plt.colorbar(c, ax=ax) @image_comparison(baseline_images=['contour_datetime_axis'], diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index f913445ac6c1..544d3ef89201 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -167,9 +167,6 @@ def baseline_images(request, fontset, index): return ['%s_%s_%02d' % (request.param, fontset, index)] -# See #7911 for why these tests are flaky and #7107 for why they are not so -# easy to fix. -@pytest.mark.flaky(reruns=3) @pytest.mark.parametrize('index, test', enumerate(math_tests), ids=[str(index) for index in range(len(math_tests))]) @pytest.mark.parametrize('fontset', @@ -184,9 +181,6 @@ def test_mathtext_rendering(baseline_images, fontset, index, test): horizontalalignment='center', verticalalignment='center') -# See #7911 for why these tests are flaky and #7107 for why they are not so -# easy to fix. -@pytest.mark.flaky(reruns=3) @pytest.mark.parametrize('index, test', enumerate(font_tests), ids=[str(index) for index in range(len(font_tests))]) @pytest.mark.parametrize('fontset', diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py index 76d9c1424212..03ac60a3e0d0 100644 --- a/lib/matplotlib/tests/test_tightlayout.py +++ b/lib/matplotlib/tests/test_tightlayout.py @@ -58,7 +58,7 @@ def test_tight_layout3(): @image_comparison(baseline_images=['tight_layout4'], - freetype_version=('2.4.5', '2.4.9')) + freetype_version=('2.5.5', '2.6.1')) def test_tight_layout4(): 'Test tight_layout for subplot2grid' diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index b11ae28ba0cc..caa5dcdaf73f 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -907,11 +907,12 @@ def get_prop_tup(self, renderer=None): need to know if the text has changed. """ x, y = self.get_unitless_position() + renderer = renderer or self._renderer return (x, y, self.get_text(), self._color, self._verticalalignment, self._horizontalalignment, hash(self._fontproperties), self._rotation, self._rotation_mode, - self.figure.dpi, id(renderer or self._renderer), + self.figure.dpi, id(renderer), getattr(renderer, '_uid', 0), self._linespacing ) diff --git a/setup.cfg.template b/setup.cfg.template index e8ba46861b60..3bb835d1def8 100644 --- a/setup.cfg.template +++ b/setup.cfg.template @@ -13,19 +13,19 @@ # set this to True. It will download and build a specific version of # FreeType, and then use that to build the ft2font extension. This # ensures that test images are exactly reproducible. -#local_freetype = False +local_freetype = True [status] # To suppress display of the dependencies and their versions # at the top of the build log, uncomment the following line: -#suppress = True +#suppress = True [packages] # There are a number of subpackages of Matplotlib that are considered # optional. All except tests are installed by default, but that can # be changed here. # -#tests = False +tests = True #sample_data = True #toolkits = True # Tests for the toolkits are only automatically installed diff --git a/setupext.py b/setupext.py index 1b2506da2c6b..15c59053c2b9 100644 --- a/setupext.py +++ b/setupext.py @@ -1341,7 +1341,7 @@ def add_flags(self, ext): default_libraries=['qhull']) else: ext.include_dirs.append('extern') - ext.sources.extend(glob.glob('extern/libqhull/*.c')) + ext.sources.extend(sorted(glob.glob('extern/libqhull/*.c'))) class TTConv(SetupPackage):