From 6dbcaf1e8114ca5337ea2b8727975234225464b8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 15 May 2017 04:27:26 -0400 Subject: [PATCH 001/433] Don't clear Spines and Axis twice in Axes.__init__. Both these classes have called their own .cla() method in their .__init__() method, so don't call it again in Axes.cla() if doing Axes.__init__(). --- lib/matplotlib/axes/_base.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 7948c14de8e9..9ac220fc1256 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -479,6 +479,7 @@ def __init__(self, fig, rect, """ % {'scale': ' | '.join( [repr(x) for x in mscale.get_scale_names()])} martist.Artist.__init__(self) + self._in_init = True if isinstance(rect, mtransforms.Bbox): self._position = rect else: @@ -576,6 +577,8 @@ def __init__(self, fig, rect, right=rcParams['ytick.right'] and rcParams['ytick.major.right'], which='major') + self._in_init = False + def __getstate__(self): # The renderer should be re-created by the figure, and then cached at # that point. @@ -965,10 +968,13 @@ def cla(self): xaxis_visible = self.xaxis.get_visible() yaxis_visible = self.yaxis.get_visible() - self.xaxis.cla() - self.yaxis.cla() - for name, spine in six.iteritems(self.spines): - spine.cla() + # Don't clear during __init__ because they're already been cleared by + # their own __init__. + if not self._in_init: + self.xaxis.cla() + self.yaxis.cla() + for name, spine in six.iteritems(self.spines): + spine.cla() self.ignore_existing_data_limits = True self.callbacks = cbook.CallbackRegistry() From 72821afa9a8126eaa6908768a9c0cc05564870e2 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 15 May 2017 20:11:05 -0400 Subject: [PATCH 002/433] Don't clear a new Axis when registering with a Spine. If the Axis was just created, calling Axis.cla is redundant because it just happened. --- lib/matplotlib/axes/_base.py | 8 ++++---- lib/matplotlib/spines.py | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 9ac220fc1256..6f0839eaf851 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -610,11 +610,11 @@ def get_window_extent(self, *args, **kwargs): def _init_axis(self): "move this out of __init__ because non-separable axes don't use it" self.xaxis = maxis.XAxis(self) - self.spines['bottom'].register_axis(self.xaxis) - self.spines['top'].register_axis(self.xaxis) + self.spines['bottom'].register_axis(self.xaxis, _init=True) + self.spines['top'].register_axis(self.xaxis, _init=True) self.yaxis = maxis.YAxis(self) - self.spines['left'].register_axis(self.yaxis) - self.spines['right'].register_axis(self.yaxis) + self.spines['left'].register_axis(self.yaxis, _init=True) + self.spines['right'].register_axis(self.yaxis, _init=True) self._update_transScale() def set_figure(self, fig): diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index c6b04fe43dba..1026c7c3a518 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -151,7 +151,7 @@ def _ensure_position_is_set(self): self._position = ('outward', 0.0) # in points self.set_position(self._position) - def register_axis(self, axis): + def register_axis(self, axis, _init=False): """register an axis An axis should be registered with its corresponding spine from @@ -159,7 +159,9 @@ def register_axis(self, axis): properties when needed. """ self.axis = axis - if self.axis is not None: + if not _init and self.axis is not None: + # Clear the axis when added, but *not* if the caller says it was + # just created. self.axis.cla() self.stale = True From 4801cbcda23fb019a43b6ddc03cf343b54f572a9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 16 May 2017 02:18:26 -0400 Subject: [PATCH 003/433] Skip extra Axis.cla in PolarAxes.__init__ also. --- lib/matplotlib/projections/polar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index a5d2b03c34b5..9976dfbe0c6b 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -245,8 +245,10 @@ def __init__(self, *args, **kwargs): If you need to interpolate data points, consider running cbook.simple_linear_interpolation on the data before passing to matplotlib.""") Axes.__init__(self, *args, **kwargs) + self._in_init = True self.set_aspect('equal', adjustable='box', anchor='C') self.cla() + self._in_init = False __init__.__doc__ = Axes.__init__.__doc__ def cla(self): From 68d43e5096ce154b5d3199709c2dee30524f2f8f Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 13 Aug 2017 17:37:10 -0700 Subject: [PATCH 004/433] Modified rrulewraper to handle timezone-aware datetimes. --- lib/matplotlib/dates.py | 98 ++++++++++++++++++++++++++++-- lib/matplotlib/tests/test_dates.py | 18 ++++++ 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index bd27bef3759a..ef536e9c1740 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -118,6 +118,7 @@ import time import math import datetime +import functools import warnings @@ -732,20 +733,105 @@ def __call__(self, x, pos=None): class rrulewrapper(object): + def __init__(self, freq, tzinfo=None, **kwargs): + kwargs['freq'] = freq + self._base_tzinfo = tzinfo - def __init__(self, freq, **kwargs): - self._construct = kwargs.copy() - self._construct["freq"] = freq - self._rrule = rrule(**self._construct) + self._update_rrule(**kwargs) def set(self, **kwargs): self._construct.update(kwargs) + + self._update_rrule(**self._construct) + + def _update_rrule(self, **kwargs): + tzinfo = self._base_tzinfo + + # rrule does not play nicely with time zones - especially pytz time + # zones, it's best to use naive zones and attach timezones once the + # datetimes are returned + if 'dtstart' in kwargs: + dtstart = kwargs['dtstart'] + if dtstart.tzinfo is not None: + if tzinfo is None: + tzinfo = dtstart.tzinfo + else: + dtstart = dtstart.astimezone(tzinfo) + + kwargs['dtstart'] = dtstart.replace(tzinfo=None) + + if 'until' in kwargs: + until = kwargs['until'] + if until.tzinfo is not None: + if tzinfo is not None: + until = until.astimezone(tzinfo) + else: + raise ValueError('until cannot be aware if dtstart ' + 'is naive and tzinfo is None') + + kwargs['until'] = until.replace(tzinfo=None) + + self._construct = kwargs.copy() + self._tzinfo = tzinfo self._rrule = rrule(**self._construct) + def _attach_tzinfo(self, dt, tzinfo): + # pytz zones are attached by "localizing" the datetime + if hasattr(tzinfo, 'localize'): + return tzinfo.localize(dt, is_dst=True) + + return dt.replace(tzinfo=tzinfo) + + def _aware_return_wrapper(self, f, returns_list=False): + """Decorator function that allows rrule methods to handle tzinfo.""" + # This is only necessary if we're actually attaching a tzinfo + if self._tzinfo is None: + return f + + # All datetime arguments must be naive. If they are not naive, they are + # converted to the _tzinfo zone before dropping the zone. + def normalize_arg(arg): + if isinstance(arg, datetime.datetime) and arg.tzinfo is not None: + if arg.tzinfo is not self._tzinfo: + arg = arg.astimezone(self._tzinfo) + + return arg.replace(tzinfo=None) + + return arg + + def normalize_args(args, kwargs): + args = tuple(normalize_arg(arg) for arg in args) + kwargs = {kw: normalize_arg(arg) for kw, arg in kwargs.items()} + + return args, kwargs + + # There are two kinds of functions we care about - ones that return + # dates and ones that return lists of dates. + if not returns_list: + def inner_func(*args, **kwargs): + args, kwargs = normalize_args(args, kwargs) + dt = f(*args, **kwargs) + return self._attach_tzinfo(dt, self._tzinfo) + else: + def inner_func(*args, **kwargs): + args, kwargs = normalize_args(args, kwargs) + dts = f(*args, **kwargs) + return [self._attach_tzinfo(dt, self._tzinfo) for dt in dts] + + return functools.wraps(f)(inner_func) + def __getattr__(self, name): if name in self.__dict__: return self.__dict__[name] - return getattr(self._rrule, name) + + f = getattr(self._rrule, name) + + if name in {'after', 'before'}: + return self._aware_return_wrapper(f) + elif name in {'xafter', 'xbefore', 'between'}: + return self._aware_return_wrapper(f, returns_list=True) + else: + return f def __setstate__(self, state): self.__dict__.update(state) @@ -1226,7 +1312,7 @@ def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None): bymonth = [x.item() for x in bymonth.astype(int)] rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday, - interval=interval, **self.hms0d) + interval=interval, **self.hms0d) RRuleLocator.__init__(self, rule, tz) diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 5a25e6182b7e..792341ee1527 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -442,6 +442,24 @@ def tz_convert(*args): _test_date2num_dst(pd.date_range, tz_convert) +@pytest.mark.parametrize("attach_tz, get_tz", [ + (lambda dt, zi: zi.localize(dt), lambda n: pytz.timezone(n)), + (lambda dt, zi: dt.replace(tzinfo=zi), lambda n: dateutil.tz.gettz(n))]) +def test_rrulewrapper(attach_tz, get_tz): + SYD = get_tz('Australia/Sydney') + + dtstart = attach_tz(datetime.datetime(2017, 4, 1, 0), SYD) + dtend = attach_tz(datetime.datetime(2017, 4, 4, 0), SYD) + + rule = mdates.rrulewrapper(freq=dateutil.rrule.DAILY, dtstart=dtstart) + + act = rule.between(dtstart, dtend) + exp = [datetime.datetime(2017, 4, 1, 13, tzinfo=dateutil.tz.tzutc()), + datetime.datetime(2017, 4, 2, 14, tzinfo=dateutil.tz.tzutc())] + + assert act == exp + + def test_DayLocator(): with pytest.raises(ValueError): mdates.DayLocator(interval=-1) From 6f73f0673c5a99e3ceea7d6633eabd09b5243eae Mon Sep 17 00:00:00 2001 From: Derek Tropf Date: Mon, 14 Aug 2017 10:14:32 -0400 Subject: [PATCH 005/433] Added RectangleSelector.geometry docstring Docstring contains details specific to RectangleSelector and does not fully generalize to EllipseSelector. --- lib/matplotlib/widgets.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 2c395250b2e5..9d4a9bd20c71 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2334,6 +2334,11 @@ def _set_active_handle(self, event): @property def geometry(self): + """ + Returns numpy.ndarray of shape (2,5) containing x (RectangleSelector.geometry[1:]) + and y (RectangleSelector.geometry[0:]) coordinates of the four corners of the rectangle starting + and ending in the top left corner. + """ if hasattr(self.to_draw, 'get_verts'): xfm = self.ax.transData.inverted() y, x = xfm.transform(self.to_draw.get_verts()).T From 3dd0bf6ca2390a1698c6c40dcb376a794fdfbd71 Mon Sep 17 00:00:00 2001 From: Derek Tropf Date: Fri, 18 Aug 2017 09:34:12 -0400 Subject: [PATCH 006/433] Fixed PEP8 issue with RectangleSelector.geoemtry docstring Wrapped docstring to <80 characters --- lib/matplotlib/widgets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 9d4a9bd20c71..3cfcac99a69c 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2335,8 +2335,9 @@ def _set_active_handle(self, event): @property def geometry(self): """ - Returns numpy.ndarray of shape (2,5) containing x (RectangleSelector.geometry[1:]) - and y (RectangleSelector.geometry[0:]) coordinates of the four corners of the rectangle starting + Returns numpy.ndarray of shape (2,5) containing x + (RectangleSelector.geometry[1,:]) and y (RectangleSelector.geometry[0,:]) + coordinates of the four corners of the rectangle starting and ending in the top left corner. """ if hasattr(self.to_draw, 'get_verts'): From 542032d1fd48abc637465de2e22192bdc33e2cc5 Mon Sep 17 00:00:00 2001 From: Derek Tropf Date: Fri, 18 Aug 2017 09:46:10 -0400 Subject: [PATCH 007/433] Corrected indentation of docstring --- lib/matplotlib/widgets.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 3cfcac99a69c..8eadc7738bef 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2334,12 +2334,13 @@ def _set_active_handle(self, event): @property def geometry(self): - """ - Returns numpy.ndarray of shape (2,5) containing x - (RectangleSelector.geometry[1,:]) and y (RectangleSelector.geometry[0,:]) - coordinates of the four corners of the rectangle starting - and ending in the top left corner. - """ + """ + Returns numpy.ndarray of shape (2,5) containing + x (RectangleSelector.geometry[1,:]) and + y (RectangleSelector.geometry[0,:]) + coordinates of the four corners of the rectangle starting + and ending in the top left corner. + """ if hasattr(self.to_draw, 'get_verts'): xfm = self.ax.transData.inverted() y, x = xfm.transform(self.to_draw.get_verts()).T From 8aafcbf639b9d4870dbca66909231a4d9e4d3f64 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 11 Jul 2017 21:24:18 -0700 Subject: [PATCH 008/433] Move start_event_loop_default to start_event_loop. There's no benefit to the end user to having a deprecation warning in start_event_loop (which gets called e.g. by ginput) -- it's essentially just there to let the devs know that they could implement a more efficient (GUI toolkit-dependent) version. So just provide the default implementations instead and add a note that GUI toolkits may override them. flush_events doesn't need to raise by default, it can just return nothing for non-interactive backends. --- lib/matplotlib/backend_bases.py | 66 ++++++------------- lib/matplotlib/backends/backend_gtk.py | 7 -- lib/matplotlib/backends/backend_gtk3.py | 8 --- lib/matplotlib/backends/backend_tkagg.py | 8 --- lib/matplotlib/backends/backend_webagg.py | 11 ---- .../backends/backend_webagg_core.py | 11 ---- 6 files changed, 19 insertions(+), 92 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 98aca9251d4c..41239b5b5bce 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2426,55 +2426,24 @@ def new_timer(self, *args, **kwargs): return TimerBase(*args, **kwargs) def flush_events(self): - """ - Flush the GUI events for the figure. Implemented only for - backends with GUIs. - """ - raise NotImplementedError + """Flush the GUI events for the figure. - def start_event_loop(self, timeout): + GUI backends likely need to reimplement this method. """ - Start an event loop. This is used to start a blocking event - loop so that interactive functions, such as ginput and - waitforbuttonpress, can wait for events. This should not be - confused with the main GUI event loop, which is always running - and has nothing to do with this. - This is implemented only for backends with GUIs. - """ - raise NotImplementedError + def start_event_loop(self, timeout=0): + """Start a blocking event loop. - def stop_event_loop(self): - """ - Stop an event loop. This is used to stop a blocking event - loop so that interactive functions, such as ginput and - waitforbuttonpress, can wait for events. + Such an event loop is used by interactive functions, such as ginput and + waitforbuttonpress, to wait for events. This should not be confused + with the main GUI event loop, which is independent and always running. - This is implemented only for backends with GUIs. - """ - raise NotImplementedError - - 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 - waitforbuttonpress, can wait for events. This should not be - confused with the main GUI event loop, which is always running - and has nothing to do with this. - - This function provides default event loop functionality based - on time.sleep that is meant to be used until event loop - functions for each of the GUI backends can be written. As - such, it throws a deprecated warning. + The event loop blocks until a callback function triggers + `stop_event_loop`, or *timeout* is reached. If *timeout* is negative, + never timeout. - This call blocks until a callback function triggers - stop_event_loop() or *timeout* is reached. If *timeout* is - <=0, never timeout. + Only GUI backends need to reimplement this method. """ - str = "Using default event loop until function specific" - str += " to this GUI is implemented" - warnings.warn(str, mplDeprecation) - if timeout <= 0: timeout = np.inf timestep = 0.01 @@ -2485,15 +2454,18 @@ def start_event_loop_default(self, timeout=0): time.sleep(timestep) counter += 1 - def stop_event_loop_default(self): - """ - Stop an event loop. This is used to stop a blocking event - loop so that interactive functions, such as ginput and - waitforbuttonpress, can wait for events. + def stop_event_loop(self): + """Stop the current blocking event loop. + Only GUI backends need to reimplement this method. """ self._looping = False + start_event_loop_default = cbook.deprecated( + "2.1", name="start_event_loop_default")(start_event_loop) + stop_event_loop_default = cbook.deprecated( + "2.1", name="stop_event_loop_default")(stop_event_loop) + def key_press_handler(event, canvas, toolbar=None): """ diff --git a/lib/matplotlib/backends/backend_gtk.py b/lib/matplotlib/backends/backend_gtk.py index 757b2b7544c0..d8d8387dca34 100644 --- a/lib/matplotlib/backends/backend_gtk.py +++ b/lib/matplotlib/backends/backend_gtk.py @@ -475,13 +475,6 @@ def flush_events(self): gtk.gdk.flush() gtk.gdk.threads_leave() - def start_event_loop(self,timeout): - FigureCanvasBase.start_event_loop_default(self,timeout) - start_event_loop.__doc__=FigureCanvasBase.start_event_loop_default.__doc__ - - def stop_event_loop(self): - FigureCanvasBase.stop_event_loop_default(self) - stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ class FigureManagerGTK(FigureManagerBase): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index a5f223a38753..df8cec6ec4a1 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -332,14 +332,6 @@ def flush_events(self): Gdk.flush() Gdk.threads_leave() - def start_event_loop(self,timeout): - FigureCanvasBase.start_event_loop_default(self,timeout) - start_event_loop.__doc__=FigureCanvasBase.start_event_loop_default.__doc__ - - def stop_event_loop(self): - FigureCanvasBase.stop_event_loop_default(self) - stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ - class FigureManagerGTK3(FigureManagerBase): """ diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index f6190d4f369e..cb9971a00dbe 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -463,14 +463,6 @@ def new_timer(self, *args, **kwargs): def flush_events(self): self._master.update() - def start_event_loop(self,timeout): - FigureCanvasBase.start_event_loop_default(self,timeout) - start_event_loop.__doc__=FigureCanvasBase.start_event_loop_default.__doc__ - - def stop_event_loop(self): - FigureCanvasBase.stop_event_loop_default(self) - stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__ - class FigureManagerTkAgg(FigureManagerBase): """ diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index e39bf2cb2bab..a1468b39c306 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -59,17 +59,6 @@ def show(self): def new_timer(self, *args, **kwargs): return TimerTornado(*args, **kwargs) - def start_event_loop(self, timeout): - backend_bases.FigureCanvasBase.start_event_loop_default( - self, timeout) - start_event_loop.__doc__ = \ - backend_bases.FigureCanvasBase.start_event_loop_default.__doc__ - - def stop_event_loop(self): - backend_bases.FigureCanvasBase.stop_event_loop_default(self) - stop_event_loop.__doc__ = \ - backend_bases.FigureCanvasBase.stop_event_loop_default.__doc__ - class WebAggApplication(tornado.web.Application): initialized = False diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index d22d00704a95..7ef40187f6fe 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -359,17 +359,6 @@ def handle_set_dpi_ratio(self, event): def send_event(self, event_type, **kwargs): self.manager._send_event(event_type, **kwargs) - def start_event_loop(self, timeout): - backend_bases.FigureCanvasBase.start_event_loop_default( - self, timeout) - start_event_loop.__doc__ = \ - backend_bases.FigureCanvasBase.start_event_loop_default.__doc__ - - def stop_event_loop(self): - backend_bases.FigureCanvasBase.stop_event_loop_default(self) - stop_event_loop.__doc__ = \ - backend_bases.FigureCanvasBase.stop_event_loop_default.__doc__ - _JQUERY_ICON_CLASSES = { 'home': 'ui-icon ui-icon-home', From 95dc08fd1604eb30090342598717f4be58a7284a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 11 Jul 2017 21:33:27 -0700 Subject: [PATCH 009/433] Let pause always run the event loop. The default `start_event_loop` is not as efficient as it could be for non-interactive backends (it runs a busy loop) but if you really care you should just call time.sleep in that case. Meanwhile, this avoids the need for third-party interactive backends to register themselves into rcsetup.interactive_bk in order to be correctly pause()able. --- lib/matplotlib/pyplot.py | 42 +++++++++++++++------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 5c78047db80b..79ee0f2deccc 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -75,8 +75,7 @@ def _backend_selection(): loop, and if not switches to a compatible one. """ backend = rcParams['backend'] - if not rcParams['backend_fallback'] or \ - backend not in _interactive_bk: + if not rcParams['backend_fallback'] or backend not in _interactive_bk: return is_agg_backend = rcParams['backend'].endswith('Agg') if 'wx' in sys.modules and not backend in ('WX', 'WXAgg'): @@ -275,33 +274,24 @@ def pause(interval): """ Pause for *interval* seconds. - If there is an active figure it will be updated and displayed, - and the GUI event loop will run during the pause. + If there is an active figure, it will be updated and displayed before the + pause, and the GUI event loop (if any) will run during the pause. - If there is no active figure, or if a non-interactive backend - is in use, this executes time.sleep(interval). - - This can be used for crude animation. For more complex - animation, see :mod:`matplotlib.animation`. - - This function is experimental; its behavior may be changed - or extended in a future release. + This can be used for crude animation. For more complex animation, see + :mod:`matplotlib.animation`. + This function is experimental; its behavior may be changed or extended in a + future release. """ - backend = rcParams['backend'] - if backend in _interactive_bk: - figManager = _pylab_helpers.Gcf.get_active() - if figManager is not None: - canvas = figManager.canvas - if canvas.figure.stale: - canvas.draw_idle() - show(block=False) - canvas.start_event_loop(interval) - return - - # No on-screen figure is active, so sleep() is all we need. - import time - time.sleep(interval) + manager = _pylab_helpers.Gcf.get_active() + if manager is not None: + canvas = manager.canvas + if canvas.figure.stale: + canvas.draw_idle() + show(block=False) + canvas.start_event_loop(interval) + else: + time.sleep(interval) @docstring.copy_dedent(matplotlib.rc) From e1484e2e4a99d181ee0d0a3a48937f30ece72b57 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 6 Aug 2017 16:26:33 -0400 Subject: [PATCH 010/433] DOC: Change text start/stop_event_loop docstrings --- lib/matplotlib/backend_bases.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 41239b5b5bce..bbc4e02b3776 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2428,21 +2428,24 @@ def new_timer(self, *args, **kwargs): def flush_events(self): """Flush the GUI events for the figure. - GUI backends likely need to reimplement this method. + Interactive backends need to reimplement this method. """ def start_event_loop(self, timeout=0): """Start a blocking event loop. - Such an event loop is used by interactive functions, such as ginput and - waitforbuttonpress, to wait for events. This should not be confused - with the main GUI event loop, which is independent and always running. + Such an event loop is used by interactive functions, such as `ginput` + and `waitforbuttonpress`, to wait for events. The event loop blocks until a callback function triggers - `stop_event_loop`, or *timeout* is reached. If *timeout* is negative, - never timeout. + `stop_event_loop`, or *timeout* is reached. - Only GUI backends need to reimplement this method. + If *timeout* is negative, never timeout. + + Only interactive backends need to reimplement this method and it relies + on `flush_events` being properly implemented. + + Interactive backends should implement this in a more native way. """ if timeout <= 0: timeout = np.inf @@ -2457,7 +2460,8 @@ def start_event_loop(self, timeout=0): def stop_event_loop(self): """Stop the current blocking event loop. - Only GUI backends need to reimplement this method. + Interactive backends need to reimplement this to match + `start_event_loop` """ self._looping = False From ecc297635f13ac0bc60861570d0e93313a491cec Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Tue, 6 Jun 2017 17:42:12 +0200 Subject: [PATCH 011/433] Fixed bug caused by wrong scoping Fixed a bug related to potentially wrong variable scope where the inner `this` of a function is used instead of the `this` of the outer scope. --- lib/matplotlib/backends/web_backend/mpl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index cecebd8e0201..cde766b88f12 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -77,7 +77,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { }; this.imageObj.onunload = function() { - this.ws.close(); + fig.ws.close(); } this.ws.onmessage = this._make_on_message_function(this); From 82f1465ea69bfead1793b6018b6c32b63b554234 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 29 Aug 2017 20:28:22 +0100 Subject: [PATCH 012/433] Fix PEP8 issues --- lib/matplotlib/widgets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 8eadc7738bef..8b4055643a74 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -2335,12 +2335,12 @@ def _set_active_handle(self, event): @property def geometry(self): """ - Returns numpy.ndarray of shape (2,5) containing - x (RectangleSelector.geometry[1,:]) and - y (RectangleSelector.geometry[0,:]) + Returns numpy.ndarray of shape (2,5) containing + x (``RectangleSelector.geometry[1,:]``) and + y (``RectangleSelector.geometry[0,:]``) coordinates of the four corners of the rectangle starting and ending in the top left corner. - """ + """ if hasattr(self.to_draw, 'get_verts'): xfm = self.ax.transData.inverted() y, x = xfm.transform(self.to_draw.get_verts()).T From 096ce517e57ce696dd6dc0ab1bc2bbf133fd0053 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 29 Aug 2017 21:02:53 +0100 Subject: [PATCH 013/433] Remove old numpy histogram note --- lib/matplotlib/axes/_axes.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 15ba96030a89..89e063302e1d 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5988,13 +5988,6 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, -------- hist2d : 2D histograms - Notes - ----- - Until numpy release 1.5, the underlying numpy histogram function was - incorrect with ``normed=True`` if bin sizes were unequal. MPL - inherited that error. It is now corrected within MPL when using - earlier numpy versions. - """ # Avoid shadowing the builtin. bin_range = range From 02cfe85fe7cccc6a9694f5455657e76eb0ca02a5 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 29 Aug 2017 21:30:23 +0100 Subject: [PATCH 014/433] Clear up variable naming --- lib/matplotlib/axes/_axes.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 89e063302e1d..9f5f3cc1d8c4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6080,8 +6080,10 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, else: hist_kwargs = dict(range=bin_range) - n = [] + # List to store all the top coordinates of the histograms + tops = [] mlast = None + # Loop through datasets for i in xrange(nx): # this will automatically overwrite bins, # so that each histogram uses the same bins @@ -6097,21 +6099,23 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, mlast = np.zeros(len(bins)-1, m.dtype) m += mlast mlast[:] = m - n.append(m) + tops.append(m) + # If a stacked density plot, normalize so the area of all the stacked + # histograms together is 1 if stacked and density: db = np.diff(bins) - for m in n: - m[:] = (m.astype(float) / db) / n[-1].sum() + for m in tops: + m[:] = (m.astype(float) / db) / tops[-1].sum() if cumulative: slc = slice(None) if cbook.is_numlike(cumulative) and cumulative < 0: slc = slice(None, None, -1) if density: - n = [(m * np.diff(bins))[slc].cumsum()[slc] for m in n] + tops = [(m * np.diff(bins))[slc].cumsum()[slc] for m in tops] else: - n = [m[slc].cumsum()[slc] for m in n] + tops = [m[slc].cumsum()[slc] for m in tops] patches = [] @@ -6129,7 +6133,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, if rwidth is not None: dr = np.clip(rwidth, 0, 1) - elif (len(n) > 1 and + elif (len(tops) > 1 and ((not stacked) or rcParams['_internal.classic_mode'])): dr = 0.8 else: @@ -6155,7 +6159,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, _barfunc = self.bar bottom_kwarg = 'bottom' - for m, c in zip(n, color): + for m, c in zip(tops, color): if bottom is None: bottom = np.zeros(len(m)) if stacked: @@ -6199,7 +6203,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, # For data that is normed to form a probability density, # set to minimum data value / logbase # (gives 1 full tick-label unit for the lowest filled bin) - ndata = np.array(n) + ndata = np.array(tops) minimum = (np.min(ndata[ndata > 0])) / logbase else: # For non-normed (density = False) data, @@ -6222,7 +6226,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, fill = (histtype == 'stepfilled') xvals, yvals = [], [] - for m in n: + for m in tops: if stacked: # starting point for drawing polygon y[0] = y[1] @@ -6285,9 +6289,9 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, p.set_label('_nolegend_') if nx == 1: - return n[0], bins, cbook.silent_list('Patch', patches[0]) + return tops[0], bins, cbook.silent_list('Patch', patches[0]) else: - return n, bins, cbook.silent_list('Lists of Patches', patches) + return tops, bins, cbook.silent_list('Lists of Patches', patches) @_preprocess_data(replace_names=["x", "y", "weights"], label_namer=None) def hist2d(self, x, y, bins=10, range=None, normed=False, weights=None, From d79d6bb99102fdda581198eee4ca77dfe9355ca1 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 29 Aug 2017 21:30:34 +0100 Subject: [PATCH 015/433] Remove un-used code --- lib/matplotlib/axes/_axes.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9f5f3cc1d8c4..2c2b246ba654 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6091,12 +6091,7 @@ def hist(self, x, bins=None, range=None, density=None, weights=None, m = m.astype(float) # causes problems later if it's an int if mlast is None: mlast = np.zeros(len(bins)-1, m.dtype) - if density and not stacked: - db = np.diff(bins) - m = (m.astype(float) / db) / m.sum() if stacked: - if mlast is None: - mlast = np.zeros(len(bins)-1, m.dtype) m += mlast mlast[:] = m tops.append(m) From 2dde48aed02e63fff1477673735fc3dce388c3bd Mon Sep 17 00:00:00 2001 From: David Stansby Date: Wed, 30 Aug 2017 22:14:17 +0100 Subject: [PATCH 016/433] Add test for density histogram: --- .../baseline_images/test_axes/hist_density.png | Bin 0 -> 17433 bytes lib/matplotlib/tests/test_axes.py | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/hist_density.png diff --git a/lib/matplotlib/tests/baseline_images/test_axes/hist_density.png b/lib/matplotlib/tests/baseline_images/test_axes/hist_density.png new file mode 100644 index 0000000000000000000000000000000000000000..d75fc1fd8849cb99aad6f782c1d9596f90f80079 GIT binary patch literal 17433 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 Date: Thu, 31 Aug 2017 11:45:53 -0400 Subject: [PATCH 017/433] Slider kwarg 'valstep' allows restricts slider values to integer multiples of 'valstep' (and valmax, if closedmax is True). --- lib/matplotlib/widgets.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 2c395250b2e5..d61322fe5cd5 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -271,9 +271,9 @@ class Slider(AxesWidget): Call :meth:`on_changed` to connect to the slider event """ - def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', - closedmin=True, closedmax=True, slidermin=None, - slidermax=None, dragging=True, **kwargs): + def __init__(self, ax, label, valmin, valmax, valstep=None, valinit=0.5, + valfmt='%1.2f', closedmin=True, closedmax=True, + slidermin=None, slidermax=None, dragging=True, **kwargs): """ Parameters ---------- @@ -334,6 +334,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', self.drag_active = False self.valmin = valmin self.valmax = valmax + self.valstep = valstep valinit = self._value_in_bounds(valinit) if valinit is None: valinit = valmin @@ -368,6 +369,8 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', def _value_in_bounds(self, val): """ Makes sure self.val is with given bounds.""" + if self.valstep: + val = round(val/self.valstep)*self.valstep if val <= self.valmin: if not self.closedmin: return From d9d08b54684f5c104a2c7650b2756aad1408276d Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 11:54:13 -0400 Subject: [PATCH 018/433] Second slider in slider_demo exhibits the valstep feature, which makes the value take on integer multiples of parameter valstep. --- examples/widgets/slider_demo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/widgets/slider_demo.py b/examples/widgets/slider_demo.py index 3edaaf73eb71..f106ce66dc42 100644 --- a/examples/widgets/slider_demo.py +++ b/examples/widgets/slider_demo.py @@ -13,6 +13,7 @@ t = np.arange(0.0, 1.0, 0.001) a0 = 5 f0 = 3 +delta_f = 5.0 s = a0*np.sin(2*np.pi*f0*t) l, = plt.plot(t, s, lw=2, color='red') plt.axis([0, 1, -10, 10]) @@ -21,7 +22,7 @@ axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor) axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor) -sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0) +sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0, valstep=delta_f) samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0) From 62066d827fb0308ad6af86d61cfb999726afdf70 Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 12:30:30 -0400 Subject: [PATCH 019/433] Better document the valstep feature. --- lib/matplotlib/widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index d61322fe5cd5..7b8f291eb08c 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -289,6 +289,9 @@ def __init__(self, ax, label, valmin, valmax, valstep=None, valinit=0.5, valmax : float The maximum value of the slider. + valstep : float, optional, default: None + If given, the slider will snap to multiples of `valstep`. + valinit : float, optional, default: 0.5 The slider initial position. From 5b448c710d22d3dad200c71b614f13ef91a8f2da Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 12:35:15 -0400 Subject: [PATCH 020/433] Don't redraw everything if we haven't actually changed the slider's value. --- lib/matplotlib/widgets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 7b8f291eb08c..4de2c3fd80b2 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -420,6 +420,8 @@ def _update(self, event): self.set_val(val) def set_val(self, val): + if self.val == val: + return xy = self.poly.xy xy[2] = val, 1 xy[3] = val, 0 From 98fbf3c1a04f5266d31cb719304665884f444b69 Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 12:47:22 -0400 Subject: [PATCH 021/433] Moved check for value change from inside set_val to its invocation in _update. --- lib/matplotlib/widgets.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 4de2c3fd80b2..b4a35a2b9932 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -416,12 +416,10 @@ def _update(self, event): event.canvas.release_mouse(self.ax) return val = self._value_in_bounds(event.xdata) - if val is not None: + if (val is not None) and (val != self.val): self.set_val(val) def set_val(self, val): - if self.val == val: - return xy = self.poly.xy xy[2] = val, 1 xy[3] = val, 0 From 794894b5fd7c6709850ff5352f7c8b2d129b0b6c Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 13:26:29 -0400 Subject: [PATCH 022/433] Replace round() with np.round() --- lib/matplotlib/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index b4a35a2b9932..f85f7d4037da 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -373,7 +373,7 @@ def __init__(self, ax, label, valmin, valmax, valstep=None, valinit=0.5, def _value_in_bounds(self, val): """ Makes sure self.val is with given bounds.""" if self.valstep: - val = round(val/self.valstep)*self.valstep + val = np.round(val/self.valstep)*self.valstep if val <= self.valmin: if not self.closedmin: return From 1ad3a6b71a2d81d21de4b3cf374a6ec37266184a Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 14:44:57 -0400 Subject: [PATCH 023/433] Moved new argument to avoid changing previous order. --- lib/matplotlib/widgets.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index f85f7d4037da..22872a45d839 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -271,9 +271,9 @@ class Slider(AxesWidget): Call :meth:`on_changed` to connect to the slider event """ - def __init__(self, ax, label, valmin, valmax, valstep=None, valinit=0.5, - valfmt='%1.2f', closedmin=True, closedmax=True, - slidermin=None, slidermax=None, dragging=True, **kwargs): + def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', + closedmin=True, closedmax=True, slidermin=None, slidermax=None, + dragging=True, valstep=None, **kwargs): """ Parameters ---------- @@ -289,9 +289,6 @@ def __init__(self, ax, label, valmin, valmax, valstep=None, valinit=0.5, valmax : float The maximum value of the slider. - valstep : float, optional, default: None - If given, the slider will snap to multiples of `valstep`. - valinit : float, optional, default: 0.5 The slider initial position. @@ -315,6 +312,9 @@ def __init__(self, ax, label, valmin, valmax, valstep=None, valinit=0.5, dragging : bool, optional, default: True If True the slider can be dragged by the mouse. + valstep : float, optional, default: None + If given, the slider will snap to multiples of `valstep`. + Notes ----- Additional kwargs are passed on to ``self.poly`` which is the @@ -419,6 +419,7 @@ def _update(self, event): if (val is not None) and (val != self.val): self.set_val(val) + def set_val(self, val): xy = self.poly.xy xy[2] = val, 1 From adb20e24df729bff9bc58330a7032e12b100bbcc Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 16:30:50 -0400 Subject: [PATCH 024/433] PEP8 compliance --- lib/matplotlib/widgets.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 22872a45d839..ca6d1fd2fcfc 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -272,8 +272,8 @@ class Slider(AxesWidget): Call :meth:`on_changed` to connect to the slider event """ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', - closedmin=True, closedmax=True, slidermin=None, slidermax=None, - dragging=True, valstep=None, **kwargs): + closedmin=True, closedmax=True, slidermin=None, + slidermax=None, dragging=True, valstep=None, **kwargs): """ Parameters ---------- @@ -419,7 +419,6 @@ def _update(self, event): if (val is not None) and (val != self.val): self.set_val(val) - def set_val(self, val): xy = self.poly.xy xy[2] = val, 1 From db6adffdb09f28fb0bf6db31deba176482856155 Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 16:31:07 -0400 Subject: [PATCH 025/433] Snap-to values behave more like np.arange now. --- lib/matplotlib/widgets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index ca6d1fd2fcfc..d133e663a676 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -373,7 +373,9 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', def _value_in_bounds(self, val): """ Makes sure self.val is with given bounds.""" if self.valstep: - val = np.round(val/self.valstep)*self.valstep + val = np.round((val - self.valmin)/self.valstep)*self.valstep + val += self.valmin + if val <= self.valmin: if not self.closedmin: return From 9e4529876f7b61d3421295d67962024f81bd4a53 Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 16:39:44 -0400 Subject: [PATCH 026/433] Added relevant whats_new --- doc/users/next_whats_new/2017-08-31_discrete-sliders.rst | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 doc/users/next_whats_new/2017-08-31_discrete-sliders.rst diff --git a/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst b/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst new file mode 100644 index 000000000000..50c7227a73ea --- /dev/null +++ b/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst @@ -0,0 +1,8 @@ +Slider UI widget can snap to discrete values +-------------------------- + +The slider UI widget can take the optional argument `valstep`. Doing so +forces the slider to take on only discrete values, starting from `valmin` and +counting up to `valmax` with steps of size `valstep`. + +If `closedmax==True`, then the slider will snap to `valmax` as well. From e23b52284e84822b654fd6ead9014b2265ff0620 Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Thu, 31 Aug 2017 16:42:42 -0400 Subject: [PATCH 027/433] Formatting --- doc/users/next_whats_new/2017-08-31_discrete-sliders.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst b/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst index 50c7227a73ea..24a2488dd3d4 100644 --- a/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst +++ b/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst @@ -1,8 +1,8 @@ Slider UI widget can snap to discrete values -------------------------- -The slider UI widget can take the optional argument `valstep`. Doing so -forces the slider to take on only discrete values, starting from `valmin` and -counting up to `valmax` with steps of size `valstep`. +The slider UI widget can take the optional argument *valstep*. Doing so +forces the slider to take on only discrete values, starting from *valmin* and +counting up to *valmax* with steps of size *valstep*. -If `closedmax==True`, then the slider will snap to `valmax` as well. +If *closedmax==True*, then the slider will snap to *valmax* as well. From 9b3543ae4851d714621daa5d21a3ec194e7c8d86 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 31 Aug 2017 22:40:52 -0400 Subject: [PATCH 028/433] MNT: future numpy only takes ints as index --- unit/memleak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/memleak.py b/unit/memleak.py index 12af02ceac84..2f0dcc9c33a3 100755 --- a/unit/memleak.py +++ b/unit/memleak.py @@ -20,7 +20,7 @@ def run_memleak_test(bench, iterations, report): tracemalloc.start() - starti = min(50, iterations / 2) + starti = int(min(50, iterations / 2)) endi = iterations malloc_arr = np.empty((endi,), dtype=np.int64) From 8d871d7065a2198057539b09f5115c9c1625c261 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 31 Aug 2017 22:46:42 -0400 Subject: [PATCH 029/433] MNT: use tight layout in output figure --- unit/memleak.py | 1 + 1 file changed, 1 insertion(+) diff --git a/unit/memleak.py b/unit/memleak.py index 2f0dcc9c33a3..284b95a5a633 100755 --- a/unit/memleak.py +++ b/unit/memleak.py @@ -77,6 +77,7 @@ def run_memleak_test(bench, iterations, report): if not report.endswith('.pdf'): report = report + '.pdf' + fig.tight_layout() fig.savefig(report, format='pdf') From bcafaf9c88620590b5c73f76aa8102d1f9d8980f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 1 Sep 2017 00:56:43 -0400 Subject: [PATCH 030/433] MNT: use int division rather than int cast --- unit/memleak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/memleak.py b/unit/memleak.py index 284b95a5a633..3b56f996de6c 100755 --- a/unit/memleak.py +++ b/unit/memleak.py @@ -20,7 +20,7 @@ def run_memleak_test(bench, iterations, report): tracemalloc.start() - starti = int(min(50, iterations / 2)) + starti = min(50, iterations // 2) endi = iterations malloc_arr = np.empty((endi,), dtype=np.int64) From 4c06cc5df2b6cdea99dbac1da827608904a88eb9 Mon Sep 17 00:00:00 2001 From: nemanja Date: Fri, 1 Sep 2017 15:44:19 +0200 Subject: [PATCH 031/433] adding webagg.address parameter to rcParams --- lib/matplotlib/backends/backend_webagg.py | 9 +++++++-- lib/matplotlib/rcsetup.py | 10 ++++++++++ lib/matplotlib/style/core.py | 2 +- matplotlibrc.template | 3 +++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index e39bf2cb2bab..8b92e16311ba 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -229,7 +229,7 @@ def __init__(self, url_prefix=''): template_path=core.FigureManagerWebAgg.get_static_file_path()) @classmethod - def initialize(cls, url_prefix='', port=None): + def initialize(cls, url_prefix='', port=None, address=None): if cls.initialized: return @@ -253,10 +253,15 @@ def random_ports(port, n): yield port + random.randint(-2 * n, 2 * n) success = None + + if address is None: + cls.address = rcParams['webagg.address'] + else: + cls.address = address cls.port = rcParams['webagg.port'] for port in random_ports(cls.port, rcParams['webagg.port_retries']): try: - app.listen(port) + app.listen(port, cls.address) except socket.error as e: if e.errno != errno.EADDRINUSE: raise diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index ae60b108f541..429c778a21be 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -885,6 +885,15 @@ def validate_animation_writer_path(p): modules["matplotlib.animation"].writers.set_dirty() return p +def validate_webagg_address(s): + if s is not None: + import socket + try: + socket.inet_aton(s) + except socket.error as e: + raise ValueError("'webagg.address' is not a valid IP address") + return s + # A validator dedicated to the named line styles, based on the items in # ls_mapper, and a list of possible strings read from Line2D.set_linestyle _validate_named_linestyle = ValidateInStrings('linestyle', @@ -943,6 +952,7 @@ def _validate_linestyle(ls): 'backend.qt4': ['PyQt4', validate_qt4], 'backend.qt5': ['PyQt5', validate_qt5], 'webagg.port': [8988, validate_int], + 'webagg.address': ['127.0.0.1', validate_webagg_address], 'webagg.open_in_browser': [True, validate_bool], 'webagg.port_retries': [50, validate_int], 'nbagg.transparent': [True, validate_bool], diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 45ac249bc07b..0d5fff2b7010 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -37,7 +37,7 @@ # A list of rcParams that should not be applied from styles STYLE_BLACKLIST = { - 'interactive', 'backend', 'backend.qt4', 'webagg.port', + 'interactive', 'backend', 'backend.qt4', 'webagg.port', 'webagg.address', 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback', 'toolbar', 'timezone', 'datapath', 'figure.max_open_warning', 'savefig.directory', 'tk.window_focus', 'docstring.hardcopy'} diff --git a/matplotlibrc.template b/matplotlibrc.template index 2a6e8b273fb5..fd592af89558 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -53,6 +53,9 @@ backend : $TEMPLATE_BACKEND # The port to use for the web server in the WebAgg backend. # webagg.port : 8888 +# The address on which the WebAgg web server should be reachable +# webagg.address : 127.0.0.1 + # If webagg.port is unavailable, a number of other random ports will # be tried until one that is available is found. # webagg.port_retries : 50 From 07597b17f479f45ed1640ab07ce2bbda71b58146 Mon Sep 17 00:00:00 2001 From: Joseph Albert Date: Tue, 5 Sep 2017 16:47:28 -0400 Subject: [PATCH 032/433] Formatting fix. --- doc/users/next_whats_new/2017-08-31_discrete-sliders.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst b/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst index 24a2488dd3d4..73a9cddfab26 100644 --- a/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst +++ b/doc/users/next_whats_new/2017-08-31_discrete-sliders.rst @@ -1,5 +1,5 @@ Slider UI widget can snap to discrete values --------------------------- +-------------------------------------------- The slider UI widget can take the optional argument *valstep*. Doing so forces the slider to take on only discrete values, starting from *valmin* and From 5b8c4c13c7c3e395bd38140086e9049f0975115f Mon Sep 17 00:00:00 2001 From: nemanja Date: Wed, 6 Sep 2017 10:29:15 +0200 Subject: [PATCH 033/433] adding webagg.address parameter to rcParams --- lib/matplotlib/rcsetup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 429c778a21be..5ef97c079235 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -892,7 +892,8 @@ def validate_webagg_address(s): socket.inet_aton(s) except socket.error as e: raise ValueError("'webagg.address' is not a valid IP address") - return s + return s + raise ValueError("'webagg.address' is not a valid IP address") # A validator dedicated to the named line styles, based on the items in # ls_mapper, and a list of possible strings read from Line2D.set_linestyle From 11b619c157cfe7246f6fb2645bdaa159c22f304a Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 7 Sep 2017 21:09:19 -0700 Subject: [PATCH 034/433] Fix pcolormesh and DatetimeIndex error --- lib/matplotlib/axes/_axes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 402382587ccf..48126b6be55e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5620,7 +5620,8 @@ def pcolormesh(self, *args, **kwargs): X, Y, C = self._pcolorargs('pcolormesh', *args, allmatch=allmatch) Ny, Nx = X.shape - + X = X.ravel() + Y = Y.ravel() # unit conversion allows e.g. datetime objects as axis values self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs) X = self.convert_xunits(X) @@ -5628,7 +5629,7 @@ def pcolormesh(self, *args, **kwargs): # convert to one dimensional arrays C = C.ravel() - coords = np.column_stack((X.flat, Y.flat)).astype(float, copy=False) + coords = np.column_stack((X, Y)).astype(float, copy=False) collection = mcoll.QuadMesh(Nx - 1, Ny - 1, coords, antialiased=antialiased, shading=shading, From fb6fa1d07f3448ed32e3690237665b206948ba03 Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Fri, 8 Sep 2017 12:07:04 +0200 Subject: [PATCH 035/433] Makes restore __init__ works with PySide --- lib/matplotlib/backends/backend_qt5.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 1d3c7cd536ae..c57241a4d32c 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -170,11 +170,8 @@ def wrapper(self, **kwargs): QtWidgets.QWidget.__init__ = cooperative_qwidget_init __init__(self, **kwargs) finally: - try: - # Restore __init__ to sip.simplewrapper.__init__. - del QtWidgets.QWidget.__init__ - except AttributeError: - pass + # Restore __init__ + QtWidgets.QWidget.__init__ = qwidget_init return wrapper From 52eb8b8397d26827305fd291d8914ad1b29e8936 Mon Sep 17 00:00:00 2001 From: Thomas VINCENT Date: Fri, 8 Sep 2017 12:09:07 +0200 Subject: [PATCH 036/433] Pass bytes to QApplication() --- lib/matplotlib/backends/backend_qt5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index c57241a4d32c..2e895195f906 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -121,7 +121,7 @@ def _create_qApp(): if display is None or not re.search(r':\d', display): raise RuntimeError('Invalid DISPLAY variable') - qApp = QtWidgets.QApplication(["matplotlib"]) + qApp = QtWidgets.QApplication([b"matplotlib"]) qApp.lastWindowClosed.connect(qApp.quit) else: qApp = app From 7a0cbc3916239df5e6af080fb69fdec5eb2f2593 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 9 Sep 2017 00:41:51 -0700 Subject: [PATCH 037/433] Remove some unused scripts. Makefile: DISTFILES is out of date (e.g. KNOWN_BUGS has been removed back in 2011) so the targets cannot be make'd. distribute_setup.py: Essentially superceded by ensurepip these days. release/win32/data/setupwin{,egg}.py: Not updated since 2009, not mentioned by any Windows install guide, and who uses eggs these days anyways? doc/utils/pylab_names.py: for example, the links it generates (to http://sd-2116.dedibox.fr/pydocweb/doc) are broken. --- Makefile | 53 --- distribute_setup.py | 559 ------------------------------ doc/utils/pylab_names.py | 54 --- release/win32/data/setupwin.py | 13 - release/win32/data/setupwinegg.py | 16 - setup.py | 3 - 6 files changed, 698 deletions(-) delete mode 100644 Makefile delete mode 100755 distribute_setup.py delete mode 100644 doc/utils/pylab_names.py delete mode 100644 release/win32/data/setupwin.py delete mode 100644 release/win32/data/setupwinegg.py diff --git a/Makefile b/Makefile deleted file mode 100644 index 2bd8077d5e6e..000000000000 --- a/Makefile +++ /dev/null @@ -1,53 +0,0 @@ -# Makefile for matplotlib - -PYTHON = `which python` -VERSION = `${PYTHON} setup.py --version` - -DISTFILES = API_CHANGES KNOWN_BUGS INSTALL README license \ - CHANGELOG Makefile INTERACTIVE \ - MANIFEST.in lib lib/matplotlib lib/dateutil lib/pytz examples setup.py - -RELEASE = matplotlib-${VERSION} - - -clean: - ${PYTHON} setup.py clean;\ - rm -f *.png *.ps *.eps *.svg *.jpg *.pdf - find . -name "_tmp*.py" | xargs rm -f;\ - find . \( -name "*~" -o -name "*.pyc" \) | xargs rm -f;\ - find unit \( -name "*.png" -o -name "*.ps" -o -name "*.pdf" -o -name "*.eps" \) | xargs rm -f - find . \( -name "#*" -o -name ".#*" -o -name ".*~" -o -name "*~" \) | xargs rm -f - - -release: ${DISTFILES} - rm -f MANIFEST;\ - ${PYTHON} setup.py sdist --formats=gztar,zip; - -pyback: - tar cvfz pyback.tar.gz *.py lib src examples/*.py unit/*.py - - -_build_osx105: - CFLAGS="-Os -arch i386 -arch ppc" LDFLAGS="-Os -arch i386 -arch ppc" python setup.py build - -build_osx105: - echo "Use 'make -f fetch deps mpl_install instead'" - - -jdh_doc_snapshot: - git pull;\ - python setup.py install --prefix=~/dev;\ - cd doc;\ - rm -rf build;\ - python make.py clean;\ - python make.py html latex sf sfpdf; - - -test: - ${PYTHON} setup.py test - - -test-coverage: - ${PYTHON} setup.py test --with-coverage --cover-package=matplotlib - - diff --git a/distribute_setup.py b/distribute_setup.py deleted file mode 100755 index 5dc7256f7924..000000000000 --- a/distribute_setup.py +++ /dev/null @@ -1,559 +0,0 @@ -#!python -"""Bootstrap distribute installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from distribute_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import os -import shutil -import sys -import time -import fnmatch -import tempfile -import tarfile -import optparse - -from distutils import log - -try: - from site import USER_SITE -except ImportError: - USER_SITE = None - -try: - import subprocess - - def _python_cmd(*args): - args = (sys.executable,) + args - return subprocess.call(args) == 0 - -except ImportError: - # will be used for python 2.3 - def _python_cmd(*args): - args = (sys.executable,) + args - # quoting arguments if windows - if sys.platform == 'win32': - def quote(arg): - if ' ' in arg: - return '"%s"' % arg - return arg - args = [quote(arg) for arg in args] - return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 - -MINIMUM_VERSION = "0.6.28" -DEFAULT_VERSION = "0.6.45" -DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" -SETUPTOOLS_FAKED_VERSION = "0.6c11" - -SETUPTOOLS_PKG_INFO = """\ -Metadata-Version: 1.0 -Name: setuptools -Version: %s -Summary: xxxx -Home-page: xxx -Author: xxx -Author-email: xxx -License: xxx -Description: xxx -""" % SETUPTOOLS_FAKED_VERSION - - -def _install(tarball, install_args=()): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) - - # installing - log.warn('Installing Distribute') - if not _python_cmd('setup.py', 'install', *install_args): - log.warn('Something went wrong during the installation.') - log.warn('See the error message above.') - # exitcode will be 2 - return 2 - finally: - os.chdir(old_wd) - shutil.rmtree(tmpdir) - - -def _build_egg(egg, tarball, to_dir): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) - - # building an egg - log.warn('Building a Distribute egg in %s', to_dir) - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) - - finally: - os.chdir(old_wd) - shutil.rmtree(tmpdir) - # returning the result - log.warn(egg) - if not os.path.exists(egg): - raise IOError('Could not build the egg.') - - -def _do_download(version, download_base, to_dir, download_delay): - egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' - % (version, sys.version_info[0], sys.version_info[1])) - if not os.path.exists(egg): - tarball = download_setuptools(version, download_base, - to_dir, download_delay) - _build_egg(egg, tarball, to_dir) - sys.path.insert(0, egg) - import setuptools - setuptools.bootstrap_install_from = egg - - -def use_setuptools(version=MINIMUM_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15, no_fake=True): - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - was_imported = 'pkg_resources' in sys.modules or \ - 'setuptools' in sys.modules - try: - try: - import pkg_resources - - # Setuptools 0.7b and later is a suitable (and preferable) - # substitute for any Distribute version. - try: - pkg_resources.require("setuptools>=0.7b") - return - except (pkg_resources.DistributionNotFound, - pkg_resources.VersionConflict): - pass - - if not hasattr(pkg_resources, '_distribute'): - if not no_fake: - _fake_setuptools() - raise ImportError - except ImportError: - return _do_download(version, download_base, to_dir, download_delay) - try: - pkg_resources.require("distribute>=" + version) - return - except pkg_resources.VersionConflict: - e = sys.exc_info()[1] - if was_imported: - sys.stderr.write( - "The required version of distribute (>=%s) is not available,\n" - "and can't be installed while this script is running. Please\n" - "install a more recent version first, using\n" - "'easy_install -U distribute'." - "\n\n(Currently using %r)\n" % (version, e.args[0])) - sys.exit(2) - else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return _do_download(version, download_base, to_dir, - download_delay) - except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, - download_delay) - finally: - if not no_fake: - _create_fake_setuptools_pkg_info(to_dir) - - -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15): - """Download distribute from a specified location and return its filename - - `version` should be a valid distribute version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download - attempt. - """ - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - try: - from urllib.request import urlopen - except ImportError: - from urllib2 import urlopen - tgz_name = "distribute-%s.tar.gz" % version - url = download_base + tgz_name - saveto = os.path.join(to_dir, tgz_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - log.warn("Downloading %s", url) - src = urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = src.read() - dst = open(saveto, "wb") - dst.write(data) - finally: - if src: - src.close() - if dst: - dst.close() - return os.path.realpath(saveto) - - -def _no_sandbox(function): - def __no_sandbox(*args, **kw): - try: - from setuptools.sandbox import DirectorySandbox - if not hasattr(DirectorySandbox, '_old'): - def violation(*args): - pass - DirectorySandbox._old = DirectorySandbox._violation - DirectorySandbox._violation = violation - patched = True - else: - patched = False - except ImportError: - patched = False - - try: - return function(*args, **kw) - finally: - if patched: - DirectorySandbox._violation = DirectorySandbox._old - del DirectorySandbox._old - - return __no_sandbox - - -def _patch_file(path, content): - """Will backup the file then patch it""" - f = open(path) - existing_content = f.read() - f.close() - if existing_content == content: - # already patched - log.warn('Already patched.') - return False - log.warn('Patching...') - _rename_path(path) - f = open(path, 'w') - try: - f.write(content) - finally: - f.close() - return True - -_patch_file = _no_sandbox(_patch_file) - - -def _same_content(path, content): - f = open(path) - existing_content = f.read() - f.close() - return existing_content == content - - -def _rename_path(path): - new_name = path + '.OLD.%s' % time.time() - log.warn('Renaming %s to %s', path, new_name) - os.rename(path, new_name) - return new_name - - -def _remove_flat_installation(placeholder): - if not os.path.isdir(placeholder): - log.warn('Unknown installation at %s', placeholder) - return False - found = False - for file in os.listdir(placeholder): - if fnmatch.fnmatch(file, 'setuptools*.egg-info'): - found = True - break - if not found: - log.warn('Could not locate setuptools*.egg-info') - return - - log.warn('Moving elements out of the way...') - pkg_info = os.path.join(placeholder, file) - if os.path.isdir(pkg_info): - patched = _patch_egg_dir(pkg_info) - else: - patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) - - if not patched: - log.warn('%s already patched.', pkg_info) - return False - # now let's move the files out of the way - for element in ('setuptools', 'pkg_resources.py', 'site.py'): - element = os.path.join(placeholder, element) - if os.path.exists(element): - _rename_path(element) - else: - log.warn('Could not find the %s element of the ' - 'Setuptools distribution', element) - return True - -_remove_flat_installation = _no_sandbox(_remove_flat_installation) - - -def _after_install(dist): - log.warn('After install bootstrap.') - placeholder = dist.get_command_obj('install').install_purelib - _create_fake_setuptools_pkg_info(placeholder) - - -def _create_fake_setuptools_pkg_info(placeholder): - if not placeholder or not os.path.exists(placeholder): - log.warn('Could not find the install location') - return - pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) - setuptools_file = 'setuptools-%s-py%s.egg-info' % \ - (SETUPTOOLS_FAKED_VERSION, pyver) - pkg_info = os.path.join(placeholder, setuptools_file) - if os.path.exists(pkg_info): - log.warn('%s already exists', pkg_info) - return - - log.warn('Creating %s', pkg_info) - try: - f = open(pkg_info, 'w') - except EnvironmentError: - log.warn("Don't have permissions to write %s, skipping", pkg_info) - return - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - - pth_file = os.path.join(placeholder, 'setuptools.pth') - log.warn('Creating %s', pth_file) - f = open(pth_file, 'w') - try: - f.write(os.path.join(os.curdir, setuptools_file)) - finally: - f.close() - -_create_fake_setuptools_pkg_info = _no_sandbox( - _create_fake_setuptools_pkg_info -) - - -def _patch_egg_dir(path): - # let's check if it's already patched - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - if os.path.exists(pkg_info): - if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): - log.warn('%s already patched.', pkg_info) - return False - _rename_path(path) - os.mkdir(path) - os.mkdir(os.path.join(path, 'EGG-INFO')) - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - f = open(pkg_info, 'w') - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - return True - -_patch_egg_dir = _no_sandbox(_patch_egg_dir) - - -def _before_install(): - log.warn('Before install bootstrap.') - _fake_setuptools() - - -def _under_prefix(location): - if 'install' not in sys.argv: - return True - args = sys.argv[sys.argv.index('install') + 1:] - for index, arg in enumerate(args): - for option in ('--root', '--prefix'): - if arg.startswith('%s=' % option): - top_dir = arg.split('root=')[-1] - return location.startswith(top_dir) - elif arg == option: - if len(args) > index: - top_dir = args[index + 1] - return location.startswith(top_dir) - if arg == '--user' and USER_SITE is not None: - return location.startswith(USER_SITE) - return True - - -def _fake_setuptools(): - log.warn('Scanning installed packages') - try: - import pkg_resources - except ImportError: - # we're cool - log.warn('Setuptools or Distribute does not seem to be installed.') - return - ws = pkg_resources.working_set - try: - setuptools_dist = ws.find( - pkg_resources.Requirement.parse('setuptools', replacement=False) - ) - except TypeError: - # old distribute API - setuptools_dist = ws.find( - pkg_resources.Requirement.parse('setuptools') - ) - - if setuptools_dist is None: - log.warn('No setuptools distribution found') - return - # detecting if it was already faked - setuptools_location = setuptools_dist.location - log.warn('Setuptools installation detected at %s', setuptools_location) - - # if --root or --preix was provided, and if - # setuptools is not located in them, we don't patch it - if not _under_prefix(setuptools_location): - log.warn('Not patching, --root or --prefix is installing Distribute' - ' in another location') - return - - # let's see if its an egg - if not setuptools_location.endswith('.egg'): - log.warn('Non-egg installation') - res = _remove_flat_installation(setuptools_location) - if not res: - return - else: - log.warn('Egg installation') - pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') - if (os.path.exists(pkg_info) and - _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): - log.warn('Already patched.') - return - log.warn('Patching...') - # let's create a fake egg replacing setuptools one - res = _patch_egg_dir(setuptools_location) - if not res: - return - log.warn('Patching complete.') - _relaunch() - - -def _relaunch(): - log.warn('Relaunching...') - # we have to relaunch the process - # pip marker to avoid a relaunch bug - _cmd1 = ['-c', 'install', '--single-version-externally-managed'] - _cmd2 = ['-c', 'install', '--record'] - if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: - sys.argv[0] = 'setup.py' - args = [sys.executable] + sys.argv - sys.exit(subprocess.call(args)) - - -def _extractall(self, path=".", members=None): - """Extract all members from the archive to the current working - directory and set owner, modification time and permissions on - directories afterwards. `path' specifies a different directory - to extract to. `members' is optional and must be a subset of the - list returned by getmembers(). - """ - import copy - import operator - from tarfile import ExtractError - directories = [] - - if members is None: - members = self - - for tarinfo in members: - if tarinfo.isdir(): - # Extract directories with a safe mode. - directories.append(tarinfo) - tarinfo = copy.copy(tarinfo) - tarinfo.mode = 448 # decimal for oct 0700 - self.extract(tarinfo, path) - - # Reverse sort directories. - if sys.version_info < (2, 4): - def sorter(dir1, dir2): - return cmp(dir1.name, dir2.name) - directories.sort(sorter) - directories.reverse() - else: - directories.sort(key=operator.attrgetter('name'), reverse=True) - - # Set correct owner, mtime and filemode on directories. - for tarinfo in directories: - dirpath = os.path.join(path, tarinfo.name) - try: - self.chown(tarinfo, dirpath) - self.utime(tarinfo, dirpath) - self.chmod(tarinfo, dirpath) - except ExtractError: - e = sys.exc_info()[1] - if self.errorlevel > 1: - raise - else: - self._dbg(1, "tarfile: %s" % e) - - -def _build_install_args(options): - """ - Build the arguments to 'python setup.py install' on the distribute package - """ - install_args = [] - if options.user_install: - if sys.version_info < (2, 6): - log.warn("--user requires Python 2.6 or later") - raise SystemExit(1) - install_args.append('--user') - return install_args - - -def _parse_args(): - """ - Parse the command line for options - """ - parser = optparse.OptionParser() - parser.add_option( - '--user', dest='user_install', action='store_true', default=False, - help='install in user site package (requires Python 2.6 or later)') - parser.add_option( - '--download-base', dest='download_base', metavar="URL", - default=DEFAULT_URL, - help='alternative URL from where to download the distribute package') - options, args = parser.parse_args() - # positional arguments are ignored - return options - - -def main(version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - options = _parse_args() - tarball = download_setuptools(download_base=options.download_base) - return _install(tarball, _build_install_args(options)) - -if __name__ == '__main__': - sys.exit(main()) diff --git a/doc/utils/pylab_names.py b/doc/utils/pylab_names.py deleted file mode 100644 index 51348f1abbd7..000000000000 --- a/doc/utils/pylab_names.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import print_function -""" -autogenerate some tables for pylab namespace -""" -from pylab import * -d = locals() - -modd = dict() -for k in sorted(d): - o = d[k] - if not callable(o): - continue - doc = getattr(o, '__doc__', None) - if doc is not None: - doc = ' - '.join([line for line in doc.split('\n') if line.strip()][:2]) - - mod = getattr(o, '__module__', None) - if mod is None: - mod = 'unknown' - - if mod is not None: - if mod.startswith('matplotlib'): - if k[0].isupper(): - k = ':class:`~%s.%s`'%(mod, k) - else: - k = ':func:`~%s.%s`'%(mod, k) - mod = ':mod:`%s`'%mod - elif mod.startswith('numpy'): - #k = '`%s <%s>`_'%(k, 'http://scipy.org/Numpy_Example_List_With_Doc#%s'%k) - k = '`%s <%s>`_'%(k, 'http://sd-2116.dedibox.fr/pydocweb/doc/%s.%s'%(mod, k)) - - - if doc is None: doc = 'TODO' - - mod, k, doc = mod.strip(), k.strip(), doc.strip()[:80] - modd.setdefault(mod, []).append((k, doc)) - -for mod in sorted(modd): - border = '*' * len(mod) - print(mod) - print(border) - - print() - funcs, docs = zip(*modd[mod]) - maxfunc = max(len(f) for f in funcs) - maxdoc = max(40, max(len(d) for d in docs)) - border = '=' * maxfunc + ' ' + '=' * maxdoc - print(border) - print('{:<{}} {:<{}}'.format('symbol', maxfunc, 'description', maxdoc)) - print(border) - for func, doc in modd[mod]: - print('{:<{}} {:<{}}'.format(func, maxfunc, doc, maxdoc)) - print(border) - print() diff --git a/release/win32/data/setupwin.py b/release/win32/data/setupwin.py deleted file mode 100644 index e1b33a3eca9d..000000000000 --- a/release/win32/data/setupwin.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import print_function -from distutils import cygwinccompiler - -try: - # Python 2.6 - # Replace the msvcr func to return an [] - cygwinccompiler.get_msvcr - cygwinccompiler.get_msvcr = lambda: [] - -except AttributeError: - pass - -execfile('setup.py') diff --git a/release/win32/data/setupwinegg.py b/release/win32/data/setupwinegg.py deleted file mode 100644 index d9340e82a009..000000000000 --- a/release/win32/data/setupwinegg.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import print_function -from distutils import cygwinccompiler - -try: - # Python 2.6 - # Replace the msvcr func to return an empty list - cygwinccompiler.get_msvcr - cygwinccompiler.get_msvcr = lambda: [] - -except AttributeError: - pass - -from setuptools import setup -execfile('setup.py', - {'additional_params' : - {'namespace_packages' : ['mpl_toolkits']}}) diff --git a/setup.py b/setup.py index 95e05a67d258..6325d4a00660 100644 --- a/setup.py +++ b/setup.py @@ -5,9 +5,6 @@ from __future__ import print_function, absolute_import from string import Template -# This needs to be the very first thing to use distribute -from distribute_setup import use_setuptools -use_setuptools() from setuptools.command.test import test as TestCommand from setuptools.command.build_ext import build_ext as BuildExtCommand From 83a290d1d673875701d03aa86e9c63d4fd30f649 Mon Sep 17 00:00:00 2001 From: Justin Cai Date: Tue, 12 Sep 2017 15:09:54 -0600 Subject: [PATCH 038/433] fixed typo --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 402382587ccf..3ed08d51f22b 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -238,7 +238,7 @@ def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs): y label labelpad : scalar, optional, default: None - spacing in points between the label and the x-axis + spacing in points between the label and the y-axis Other Parameters ---------------- From 46e9c31c2a655cf94c23e082fb2d542330746bac Mon Sep 17 00:00:00 2001 From: David Stansby Date: Thu, 14 Sep 2017 15:05:18 +0100 Subject: [PATCH 039/433] Convert clabel docstring to numpydoc --- lib/matplotlib/contour.py | 63 +++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index a435cf467031..61ac8fc79309 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -75,12 +75,14 @@ def clabel(self, *args, **kwargs): only labels contours listed in *v*. - Optional keyword arguments: + Parameters + ---------- + fontsize : string or float, optional + Size in points or relative size e.g., 'smaller', 'x-large' - *fontsize*: - size in points or relative size e.g., 'smaller', 'x-large' + colors : + Color of each label - *colors*: - if *None*, the color of each label matches the color of the corresponding contour @@ -91,47 +93,50 @@ def clabel(self, *args, **kwargs): different labels will be plotted in different colors in the order specified - *inline*: - controls whether the underlying contour is removed or - not. Default is *True*. + inline : bool, optional + If ``True`` the underlying contour is removed where the label is + placed. Default is ``True``. + + inline_spacing : float, optional + Space in pixels to leave on each side of label when + placing inline. Defaults to 5. + + This spacing will be exact for labels at locations where the + contour is straight, less so for labels on curved contours. - *inline_spacing*: - space in pixels to leave on each side of label when - placing inline. Defaults to 5. This spacing will be - exact for labels at locations where the contour is - straight, less so for labels on curved contours. + fmt : string or dict, optional + A format string for the label. Default is '%1.3f' - *fmt*: - a format string for the label. Default is '%1.3f' Alternatively, this can be a dictionary matching contour levels with arbitrary strings to use for each contour level (i.e., fmt[level]=string), or it can be any callable, such as a :class:`~matplotlib.ticker.Formatter` instance, that returns a string when called with a numeric contour level. - *manual*: - if *True*, contour labels will be placed manually using - mouse clicks. Click the first button near a contour to + manual : bool or iterable, optional + If ``True``, contour labels will be placed manually using + mouse clicks. Click the first button near a contour to add a label, click the second button (or potentially both - mouse buttons at once) to finish adding labels. The third + mouse buttons at once) to finish adding labels. The third button can be used to remove the last label added, but - only if labels are not inline. Alternatively, the keyboard + only if labels are not inline. Alternatively, the keyboard can be used to select label locations (enter to end label placement, delete or backspace act like the third mouse button, and any other key will select a label location). - *manual* can be an iterable object of x,y tuples. Contour labels - will be created as if mouse is clicked at each x,y positions. + *manual* can also be an iterable object of x,y tuples. + Contour labels will be created as if mouse is clicked at each + x,y positions. - *rightside_up*: - if *True* (default), label rotations will always be plus - or minus 90 degrees from level. + rightside_up : bool, optional + If ``True``, label rotations will always be plus + or minus 90 degrees from level. Default is ``True``. - *use_clabeltext*: - if *True* (default is False), ClabelText class (instead of - matplotlib.Text) is used to create labels. ClabelText - recalculates rotation angles of texts during the drawing time, - therefore this can be used if aspect of the axes changes. + use_clabeltext : bool, optional + If ``True``, ClabelText class (instead of matplotlib.Text) + is used to create labels. ClabelText recalculates rotation angles + of texts during the drawing time, therefore this can be used if + aspect of the axes changes. Default is ``False``. """ """ From a83a102f442b041a40d20c491b3fa87d41ae0f2d Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Thu, 14 Sep 2017 11:59:10 -0700 Subject: [PATCH 040/433] Fix wx_compat code for wxPython >= 4.0.0b2 --- lib/matplotlib/backends/wx_compat.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/backends/wx_compat.py b/lib/matplotlib/backends/wx_compat.py index 0d2bd409ed86..64918ce565ce 100644 --- a/lib/matplotlib/backends/wx_compat.py +++ b/lib/matplotlib/backends/wx_compat.py @@ -145,12 +145,23 @@ def _AddTool(parent, wx_ids, text, bmp, tooltip_text): kind = wx.ITEM_CHECK else: kind = wx.ITEM_NORMAL - parent.AddTool(wx_ids[text], label=text, - bitmap=bmp, - bmpDisabled=wx.NullBitmap, - shortHelpString=text, - longHelpString=tooltip_text, - kind=kind) + + if LooseVersion(wx.VERSION_STRING) >= LooseVersion("4.0.0b2"): + kwargs = dict(label=text, + bitmap=bmp, + bmpDisabled=wx.NullBitmap, + shortHelp=text, + longHelp=tooltip_text, + kind=kind) + else: + kwargs = dict(label=text, + bitmap=bmp, + bmpDisabled=wx.NullBitmap, + shortHelpString=text, + longHelpString=tooltip_text, + kind=kind) + + parent.AddTool(wx_ids[text], **kwargs) else: if text in ['Pan', 'Zoom']: parent.AddCheckTool( From ed537a15024c60ba76371c972519403ba53f9c23 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 14 Sep 2017 16:43:00 -0700 Subject: [PATCH 041/433] Use svg zenodo badges throughout. --- doc/_templates/citing.html | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/_templates/citing.html b/doc/_templates/citing.html index 7989799b6dca..1c3005fe169b 100644 --- a/doc/_templates/citing.html +++ b/doc/_templates/citing.html @@ -29,17 +29,17 @@

Citing Matplotlib

DOIs

-
v2.0.2
DOI
-
v2.0.1
DOI
-
v2.0.0
DOI
-
v1.5.3
10.5281/zenodo.61948
-
v1.5.2
10.5281/zenodo.56926
-
v1.5.1
10.5281/zenodo.44579
-
v1.5.0
10.5281/zenodo.32914
-
v1.4.3
10.5281/zenodo.15423
-
v1.4.2
10.5281/zenodo.12400
-
v1.4.1
10.5281/zenodo.12287
-
v1.4.0
10.5281/zenodo.11451
+
v2.0.2
10.5281/zenodo.573577.svg
+
v2.0.1
10.5281/zenodo.570311.svg
+
v2.0.0
10.5281/zenodo.248351.svg
+
v1.5.3
10.5281/zenodo.61948
+
v1.5.2
10.5281/zenodo.56926
+
v1.5.1
10.5281/zenodo.44579
+
v1.5.0
10.5281/zenodo.32914
+
v1.4.3
10.5281/zenodo.15423
+
v1.4.2
10.5281/zenodo.12400
+
v1.4.1
10.5281/zenodo.12287
+
v1.4.0
10.5281/zenodo.11451
{% endblock %} From 5285e76ebd21b52cdb98d23b424d768f77e8bda7 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Fri, 15 Sep 2017 10:47:31 +1000 Subject: [PATCH 042/433] Change axes.prop_cycle to single line in mplrc Otherwise the example is invalid because the matplotlibrc parser doesn't understand multi-line commands. See #9184. --- matplotlibrc.template | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/matplotlibrc.template b/matplotlibrc.template index 991d200860e5..0e3aff1c3c6c 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -330,14 +330,9 @@ backend : $TEMPLATE_BACKEND #axes.unicode_minus : True # use unicode for the minus symbol # rather than hyphen. See # http://en.wikipedia.org/wiki/Plus_and_minus_signs#Character_codes -#axes.prop_cycle : cycler('color', -# ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', -# '9467bd', '8c564b', 'e377c2', '7f7f7f', -# 'bcbd22', '17becf']) - # color cycle for plot lines - # as list of string colorspecs: - # single letter, long name, or - # web-style hex +# axes.prop_cycle : cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf']) + # color cycle for plot lines as list of string + # colorspecs: single letter, long name, or web-style hex #axes.autolimit_mode : data # How to scale axes limits to the data. # Use "data" to use data limits, plus some margin # Use "round_number" move to the nearest "round" number From b1dbf5d1d86734536ca6a8dd042f3e4af9d58c70 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 15 Sep 2017 12:07:19 +0100 Subject: [PATCH 043/433] Small fixes to clabel docstring --- lib/matplotlib/contour.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 61ac8fc79309..b67f905426d5 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -78,7 +78,8 @@ def clabel(self, *args, **kwargs): Parameters ---------- fontsize : string or float, optional - Size in points or relative size e.g., 'smaller', 'x-large' + Size in points or relative size e.g., 'smaller', 'x-large'. + See `Text.set_size` for accepted string values. colors : Color of each label @@ -133,8 +134,8 @@ def clabel(self, *args, **kwargs): or minus 90 degrees from level. Default is ``True``. use_clabeltext : bool, optional - If ``True``, ClabelText class (instead of matplotlib.Text) - is used to create labels. ClabelText recalculates rotation angles + If ``True``, `ClabelText` class (instead of `Text`) is used to + create labels. `ClabelText` recalculates rotation angles of texts during the drawing time, therefore this can be used if aspect of the axes changes. Default is ``False``. """ @@ -149,7 +150,7 @@ def clabel(self, *args, **kwargs): Once these attributes are set, clabel passes control to the labels method (case of automatic label placement) or - BlockingContourLabeler (case of manual label placement). + `BlockingContourLabeler` (case of manual label placement). """ fontsize = kwargs.get('fontsize', None) From 328dda5adf32c250dbe30634327c3cb26a330dc3 Mon Sep 17 00:00:00 2001 From: et2010 Date: Fri, 15 Sep 2017 20:25:37 +0800 Subject: [PATCH 044/433] Fix typo 'anitialising' => 'antialiasing' --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3ed08d51f22b..e4e9e9d61efc 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1391,7 +1391,7 @@ def plot(self, *args, **kwargs): The *kwargs* can be used to set line properties (any property that has a ``set_*`` method). You can use this to set a line label (for auto - legends), linewidth, anitialising, marker face color, etc. Here is an + legends), linewidth, antialiasing, marker face color, etc. Here is an example:: plot([1,2,3], [1,2,3], 'go-', label='line 1', linewidth=2) From 1f982fb37f01679d143aa0f804a28db2fa8b2fa5 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Fri, 15 Sep 2017 10:25:50 -0700 Subject: [PATCH 045/433] Further refactoring and simplification of _AddTool. Changes to use DoAddTool in Classic, which is the same as AddTool in Phoenix. Includes a note about even more possible simplification when support for all pre-release builds of Phoenix is dropped. --- lib/matplotlib/backends/wx_compat.py | 60 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/lib/matplotlib/backends/wx_compat.py b/lib/matplotlib/backends/wx_compat.py index 64918ce565ce..bc2a0f746f0a 100644 --- a/lib/matplotlib/backends/wx_compat.py +++ b/lib/matplotlib/backends/wx_compat.py @@ -139,35 +139,37 @@ StockCursor = wx.StockCursor +# wxPython Classic's DoAddTool has become AddTool in Phoenix. Otherwise +# they are the same, except for early betas and prerelease builds of +# Phoenix. This function provides a shim that does the RightThing based on +# which wxPython is in use. def _AddTool(parent, wx_ids, text, bmp, tooltip_text): + if text in ['Pan', 'Zoom']: + kind = wx.ITEM_CHECK + else: + kind = wx.ITEM_NORMAL if is_phoenix: - if text in ['Pan', 'Zoom']: - kind = wx.ITEM_CHECK - else: - kind = wx.ITEM_NORMAL - - if LooseVersion(wx.VERSION_STRING) >= LooseVersion("4.0.0b2"): - kwargs = dict(label=text, - bitmap=bmp, - bmpDisabled=wx.NullBitmap, - shortHelp=text, - longHelp=tooltip_text, - kind=kind) - else: - kwargs = dict(label=text, - bitmap=bmp, - bmpDisabled=wx.NullBitmap, - shortHelpString=text, - longHelpString=tooltip_text, - kind=kind) - - parent.AddTool(wx_ids[text], **kwargs) + add_tool = parent.AddTool + else: + add_tool = parent.DoAddTool + + if not is_phoenix or LooseVersion(wx.VERSION_STRING) >= LooseVersion("4.0.0b2"): + # NOTE: when support for Phoenix prior to 4.0.0b2 is dropped then + # all that is needed is this clause, and the if and else clause can + # be removed. + kwargs = dict(label=text, + bitmap=bmp, + bmpDisabled=wx.NullBitmap, + shortHelp=text, + longHelp=tooltip_text, + kind=kind) else: - if text in ['Pan', 'Zoom']: - parent.AddCheckTool( - wx_ids[text], - bmp, - shortHelp=text, - longHelp=tooltip_text) - else: - parent.AddSimpleTool(wx_ids[text], bmp, text, tooltip_text) + kwargs = dict(label=text, + bitmap=bmp, + bmpDisabled=wx.NullBitmap, + shortHelpString=text, + longHelpString=tooltip_text, + kind=kind) + + return add_tool(wx_ids[text], **kwargs) + From 849fa26ca1ba7228ad3a384b267f54846a858410 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sat, 16 Sep 2017 12:42:17 +0100 Subject: [PATCH 046/433] Convert tick-setting methods to docstrings --- lib/matplotlib/axes/_base.py | 65 ++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index eeadfc87a1e0..12fe0704c027 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2993,7 +2993,14 @@ def set_xticks(self, ticks, minor=False): """ Set the x ticks with list of *ticks* - ACCEPTS: sequence of floats + Parameters + ---------- + ticks : sequence + Sequence of ticks + + minor : bool, optional + If ``False`` sets major ticks, if ``True`` sets minor ticks. + Default is ``False``. """ ret = self.xaxis.set_ticks(ticks, minor=minor) self.stale = True @@ -3001,16 +3008,24 @@ def set_xticks(self, ticks, minor=False): def get_xmajorticklabels(self): """ - Get the xtick labels as a list of :class:`~matplotlib.text.Text` - instances. + Get the xtick major labels + + Returns + ------- + labels : list + List of :class:`~matplotlib.text.Text` instancess """ return cbook.silent_list('Text xticklabel', self.xaxis.get_majorticklabels()) def get_xminorticklabels(self): """ - Get the x minor tick labels as a list of - :class:`matplotlib.text.Text` instances. + Get the x minor tick labels + + Returns + ------- + labels : list + List of :class:`~matplotlib.text.Text` instances """ return cbook.silent_list('Text xticklabel', self.xaxis.get_minorticklabels()) @@ -3299,28 +3314,38 @@ def set_yticks(self, ticks, minor=False): """ Set the y ticks with list of *ticks* - ACCEPTS: sequence of floats - - Keyword arguments: + Parameters + ---------- + ticks : sequence + Sequence of ticks - *minor*: [ *False* | *True* ] - Sets the minor ticks if *True* + minor : bool, optional + If ``False`` sets major ticks, if ``True`` sets minor ticks. + Default is ``False``. """ ret = self.yaxis.set_ticks(ticks, minor=minor) return ret def get_ymajorticklabels(self): """ - Get the major y tick labels as a list of - :class:`~matplotlib.text.Text` instances. + Get the major y tick labels + + Returns + ------- + labels : list + List of :class:`~matplotlib.text.Text` instancess """ return cbook.silent_list('Text yticklabel', self.yaxis.get_majorticklabels()) def get_yminorticklabels(self): """ - Get the minor y tick labels as a list of - :class:`~matplotlib.text.Text` instances. + Get the minor y tick labels + + Returns + ------- + labels : list + List of :class:`~matplotlib.text.Text` instancess """ return cbook.silent_list('Text yticklabel', self.yaxis.get_minorticklabels()) @@ -3389,8 +3414,10 @@ def xaxis_date(self, tz=None): """ Sets up x-axis ticks and labels that treat the x data as dates. - *tz* is a timezone string or :class:`tzinfo` instance. - Defaults to rc value. + Parameters + ---------- + tz : string or :class:`tzinfo` instance, optional + Timezone string or timezone. Defaults to rc value. """ # should be enough to inform the unit conversion interface # dates are coming in @@ -3400,8 +3427,10 @@ def yaxis_date(self, tz=None): """ Sets up y-axis ticks and labels that treat the y data as dates. - *tz* is a timezone string or :class:`tzinfo` instance. - Defaults to rc value. + Parameters + ---------- + tz : string or :class:`tzinfo` instance, optional + Timezone string or timezone. Defaults to rc value. """ self.yaxis.axis_date(tz) From 31afe346c8ba06dff59e74b9d6774d68ee818c5a Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sun, 17 Sep 2017 16:10:48 +0100 Subject: [PATCH 047/433] Clean conda Comment out cache Try updating conda Re-instate cache, and clean after conda update Remove quiet --- .appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 8676e8064e54..7fa813243f91 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -62,7 +62,9 @@ install: - set PATH=%CONDA_INSTALL_LOCN%;%CONDA_INSTALL_LOCN%\scripts;%PATH%; - set PYTHONUNBUFFERED=1 # for obvci_appveyor_python_build_env.cmd - - conda install -c conda-forge --yes --quiet obvious-ci + - conda update --all --yes + - conda clean --all --yes + - conda install -c conda-forge --yes obvious-ci # for msinttypes and newer stuff - conda config --prepend channels conda-forge - conda config --set show_channel_urls yes From b175cf759f9611f69b63b15eeab799d3dc0c5e8c Mon Sep 17 00:00:00 2001 From: David Stansby Date: Sun, 17 Sep 2017 17:09:23 +0100 Subject: [PATCH 048/433] Pin anaconda-client version --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 7fa813243f91..8d8d48ad17a1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -63,7 +63,7 @@ install: - set PYTHONUNBUFFERED=1 # for obvci_appveyor_python_build_env.cmd - conda update --all --yes - - conda clean --all --yes + - conda install anaconda-client=1.6.3 --yes - conda install -c conda-forge --yes obvious-ci # for msinttypes and newer stuff - conda config --prepend channels conda-forge From ae57ee66c2dd20959bea2a2d62c2e6e31bb5ae9d Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 17 Sep 2017 00:32:25 -0700 Subject: [PATCH 049/433] Don't fail on empty autoscale_None. We reuse the same strategy as in the default Normalize.autoscale_None. First convert the input to an array because that's what np.size and np.ma.min/max do anyways, so it's more efficient to do it just once. --- lib/matplotlib/colors.py | 50 ++++++++++++++++-------------- lib/matplotlib/tests/test_image.py | 28 ++++++++++------- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index a5546309929b..9277d2b12fbe 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -961,15 +961,17 @@ def autoscale(self, A): """ Set *vmin*, *vmax* to min, max of *A*. """ - self.vmin = np.ma.min(A) - self.vmax = np.ma.max(A) + A = np.asanyarray(A) + self.vmin = A.min() + self.vmax = A.max() def autoscale_None(self, A): - ' autoscale only None-valued vmin or vmax' - if self.vmin is None and np.size(A) > 0: - self.vmin = np.ma.min(A) - if self.vmax is None and np.size(A) > 0: - self.vmax = np.ma.max(A) + """autoscale only None-valued vmin or vmax.""" + A = np.asanyarray(A) + if self.vmin is None and A.size: + self.vmin = A.min() + if self.vmax is None and A.size: + self.vmax = A.max() def scaled(self): 'return true if vmin and vmax set' @@ -1037,14 +1039,14 @@ def autoscale(self, A): self.vmax = np.ma.max(A) def autoscale_None(self, A): - ' autoscale only None-valued vmin or vmax' + """autoscale only None-valued vmin or vmax.""" if self.vmin is not None and self.vmax is not None: return A = np.ma.masked_less_equal(A, 0, copy=False) - if self.vmin is None: - self.vmin = np.ma.min(A) - if self.vmax is None: - self.vmax = np.ma.max(A) + if self.vmin is None and A.size: + self.vmin = A.min() + if self.vmax is None and A.size: + self.vmax = A.max() class SymLogNorm(Normalize): @@ -1153,13 +1155,14 @@ def autoscale(self, A): self._transform_vmin_vmax() def autoscale_None(self, A): - """ autoscale only None-valued vmin or vmax """ + """autoscale only None-valued vmin or vmax.""" if self.vmin is not None and self.vmax is not None: pass - if self.vmin is None: - self.vmin = np.ma.min(A) - if self.vmax is None: - self.vmax = np.ma.max(A) + A = np.asanyarray(A) + if self.vmin is None and A.size: + self.vmin = A.min() + if self.vmax is None and A.size: + self.vmax = A.max() self._transform_vmin_vmax() @@ -1223,20 +1226,19 @@ def autoscale(self, A): self.vmin = 0 warnings.warn("Power-law scaling on negative values is " "ill-defined, clamping to 0.") - self.vmax = np.ma.max(A) def autoscale_None(self, A): - ' autoscale only None-valued vmin or vmax' - if self.vmin is None and np.size(A) > 0: - self.vmin = np.ma.min(A) + """autoscale only None-valued vmin or vmax.""" + A = np.asanyarray(A) + if self.vmin is None and A.size: + self.vmin = A.min() if self.vmin < 0: self.vmin = 0 warnings.warn("Power-law scaling on negative values is " "ill-defined, clamping to 0.") - - if self.vmax is None and np.size(A) > 0: - self.vmax = np.ma.max(A) + if self.vmax is None and A.size: + self.vmax = A.max() class BoundaryNorm(Normalize): diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 69c2f1f27b18..ba4e2dd80f99 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -2,29 +2,26 @@ unicode_literals) import six + +from copy import copy import io import os import warnings import numpy as np +from numpy import ma from numpy.testing import assert_array_equal -from matplotlib.testing.decorators import image_comparison +from matplotlib import ( + colors, image as mimage, mlab, patches, pyplot as plt, + rc_context, rcParams) from matplotlib.image import (AxesImage, BboxImage, FigureImage, NonUniformImage, PcolorImage) +from matplotlib.testing.decorators import image_comparison from matplotlib.transforms import Bbox, Affine2D, TransformedBbox -from matplotlib import rcParams, rc_context -from matplotlib import patches -import matplotlib.pyplot as plt -from matplotlib import mlab import pytest -from copy import copy -from numpy import ma -import matplotlib.image as mimage -import matplotlib.colors as colors - try: from PIL import Image @@ -789,9 +786,16 @@ def test_imshow_flatfield(): im.set_clim(.5, 1.5) -def test_empty_imshow(): +@pytest.mark.parametrize( + "make_norm", + [colors.Normalize, + colors.LogNorm, + lambda: colors.SymLogNorm(1), + lambda: colors.PowerNorm(1)]) +@pytest.mark.filterwarnings("ignore:Attempting to set identical left==right") +def test_empty_imshow(make_norm): fig, ax = plt.subplots() - im = ax.imshow([[]]) + im = ax.imshow([[]], norm=make_norm()) im.set_extent([-5, 5, -5, 5]) fig.canvas.draw() From e12db84a50fb9742eda5a50f7ca8e2f8609b3b49 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 18 Sep 2017 15:24:19 -0400 Subject: [PATCH 050/433] TST: add test for pandas + pcolormesh --- lib/matplotlib/tests/test_axes.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index b96b525aa439..108eea4b7068 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4929,6 +4929,17 @@ def test_broken_barh_empty(): ax.broken_barh([], (.1, .5)) +def test_pandas_pcolormesh(): + pd = pytest.importorskip('pandas') + + time = pd.date_range('2000-01-01', periods=10) + depth = np.arange(20) + data = np.random.rand(20,10) + + fig, ax = plt.subplots() + ax.pcolormesh(time, depth, data) + + def test_pandas_indexing_dates(): pd = pytest.importorskip('pandas') From b4acccfde93442a7d883575c704d1185e3168aea Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 19 Sep 2017 09:09:32 +0100 Subject: [PATCH 051/433] Minor fixes to tick docstrings --- lib/matplotlib/axes/_base.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 12fe0704c027..24c2d48f9871 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2995,8 +2995,8 @@ def set_xticks(self, ticks, minor=False): Parameters ---------- - ticks : sequence - Sequence of ticks + ticks : list + List of x-axis tick locations minor : bool, optional If ``False`` sets major ticks, if ``True`` sets minor ticks. @@ -3013,7 +3013,7 @@ def get_xmajorticklabels(self): Returns ------- labels : list - List of :class:`~matplotlib.text.Text` instancess + List of :class:`~matplotlib.text.Text` instances """ return cbook.silent_list('Text xticklabel', self.xaxis.get_majorticklabels()) @@ -3317,7 +3317,7 @@ def set_yticks(self, ticks, minor=False): Parameters ---------- ticks : sequence - Sequence of ticks + List of y-axis tick locations minor : bool, optional If ``False`` sets major ticks, if ``True`` sets minor ticks. @@ -3333,7 +3333,7 @@ def get_ymajorticklabels(self): Returns ------- labels : list - List of :class:`~matplotlib.text.Text` instancess + List of :class:`~matplotlib.text.Text` instances """ return cbook.silent_list('Text yticklabel', self.yaxis.get_majorticklabels()) @@ -3345,7 +3345,7 @@ def get_yminorticklabels(self): Returns ------- labels : list - List of :class:`~matplotlib.text.Text` instancess + List of :class:`~matplotlib.text.Text` instances """ return cbook.silent_list('Text yticklabel', self.yaxis.get_minorticklabels()) From ed44f27ee16e2676f86c137768ba3afd4f1f6da8 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 19 Sep 2017 12:21:30 -0700 Subject: [PATCH 052/433] get proper renderer width and height in FigureImage --- lib/matplotlib/image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 0602fae17c8d..e8b3cf35f689 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1119,7 +1119,8 @@ def get_extent(self): def make_image(self, renderer, magnification=1.0, unsampled=False): bbox = Bbox([[self.ox, self.oy], [self.ox + self._A.shape[1], self.oy + self._A.shape[0]]]) - clip = Bbox([[0, 0], [renderer.width, renderer.height]]) + width, height = renderer.get_canvas_width_height() + clip = Bbox([[0, 0], [width, height]]) return self._make_image( self._A, bbox, bbox, clip, magnification=magnification, unsampled=unsampled, round_to_pixel_border=False) From fb01a9101bc42753152e1d8c0e00e962183dea15 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 19 Sep 2017 16:44:12 -0700 Subject: [PATCH 053/433] get proper renderer width and height in FigureImage --- lib/matplotlib/image.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index e8b3cf35f689..abd1030e7ab2 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1117,13 +1117,18 @@ def get_extent(self): -0.5 + self.oy, numrows-0.5 + self.oy) def make_image(self, renderer, magnification=1.0, unsampled=False): - bbox = Bbox([[self.ox, self.oy], - [self.ox + self._A.shape[1], self.oy + self._A.shape[0]]]) - width, height = renderer.get_canvas_width_height() + fac = renderer.dpi/self.figure.dpi + bbox = Bbox([[self.ox/fac, self.oy/fac], + [(self.ox/fac + self._A.shape[1]), + (self.oy/fac + self._A.shape[0])]]) + width, height = self.figure.get_size_inches() + width *= renderer.dpi + height *= renderer.dpi clip = Bbox([[0, 0], [width, height]]) + return self._make_image( - self._A, bbox, bbox, clip, magnification=magnification, - unsampled=unsampled, round_to_pixel_border=False) + self._A, bbox, bbox, clip, magnification=magnification / fac, + unsampled=False, round_to_pixel_border=False) def set_data(self, A): """Set the image array.""" From 22091d6a1a0e286608e11e0206893afb22ef5512 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 19 Sep 2017 16:46:12 -0700 Subject: [PATCH 054/433] get proper renderer width and height in FigureImage --- lib/matplotlib/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index abd1030e7ab2..16a0c9d056d0 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1128,7 +1128,7 @@ def make_image(self, renderer, magnification=1.0, unsampled=False): return self._make_image( self._A, bbox, bbox, clip, magnification=magnification / fac, - unsampled=False, round_to_pixel_border=False) + unsampled=unsampled, round_to_pixel_border=False) def set_data(self, A): """Set the image array.""" From 272b377a21cd3c5031c8a6761b618a8c85ec5106 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 19 Sep 2017 22:05:38 -0700 Subject: [PATCH 055/433] Fixed tests to include svg and pdf for figimage --- lib/matplotlib/tests/test_image.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index ba4e2dd80f99..a4aee30557e6 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -72,11 +72,12 @@ def test_interp_nearest_vs_none(): ax2.set_title('interpolation nearest') -@image_comparison(baseline_images=['figimage-0', 'figimage-1'], extensions=['png']) +@image_comparison(baseline_images=['figimage-0'], + extensions=['png','svg','pdf']) def test_figimage(): 'test the figimage method' - for suppressComposite in False, True: + for suppressComposite in [False]: fig = plt.figure(figsize=(2,2), dpi=100) fig.suppressComposite = suppressComposite x,y = np.ix_(np.arange(100.0)/100.0, np.arange(100.0)/100.0) @@ -89,6 +90,25 @@ def test_figimage(): fig.figimage(img[:,::-1], xo=100, yo=0, origin='lower') fig.figimage(img[::-1,::-1], xo=100, yo=100, origin='lower') +@image_comparison(baseline_images=['figimage-1'], + extensions=['png','svg','pdf']) +def test_figimage(): + 'test the figimage method' + + for suppressComposite in [True]: + fig = plt.figure(figsize=(2,2), dpi=100) + fig.suppressComposite = suppressComposite + x,y = np.ix_(np.arange(100.0)/100.0, np.arange(100.0)/100.0) + z = np.sin(x**2 + y**2 - x*y) + c = np.sin(20*x**2 + 50*y**2) + img = z + c/5 + + fig.figimage(img, xo=0, yo=0, origin='lower') + fig.figimage(img[::-1,:], xo=0, yo=100, origin='lower') + fig.figimage(img[:,::-1], xo=100, yo=0, origin='lower') + fig.figimage(img[::-1,::-1], xo=100, yo=100, origin='lower') + + def test_image_python_io(): fig = plt.figure() ax = fig.add_subplot(111) From 90aab86d029f3073d23930c7b83edb25a15b05c6 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 19 Sep 2017 22:08:32 -0700 Subject: [PATCH 056/433] Put a bit of commenting in --- lib/matplotlib/image.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index 16a0c9d056d0..ea2331cb48a7 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -1118,6 +1118,9 @@ def get_extent(self): def make_image(self, renderer, magnification=1.0, unsampled=False): fac = renderer.dpi/self.figure.dpi + # fac here is to account for pdf, eps, svg backends where + # figure.dpi is set to 72. This means we need to scale the + # image (using magification) and offset it appropriately. bbox = Bbox([[self.ox/fac, self.oy/fac], [(self.ox/fac + self._A.shape[1]), (self.oy/fac + self._A.shape[0])]]) From 013d2e2e70d99ce6f651b259a5eb5d760da85ec3 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 19 Sep 2017 22:16:10 -0700 Subject: [PATCH 057/433] added baseline images --- .../baseline_images/test_image/figimage-0.pdf | Bin 0 -> 59614 bytes .../baseline_images/test_image/figimage-0.svg | 29 ++++++++++++++++++ .../baseline_images/test_image/figimage-1.pdf | Bin 0 -> 59614 bytes .../baseline_images/test_image/figimage-1.svg | 29 ++++++++++++++++++ lib/matplotlib/tests/test_image.py | 2 +- 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_image/figimage-0.pdf create mode 100644 lib/matplotlib/tests/baseline_images/test_image/figimage-0.svg create mode 100644 lib/matplotlib/tests/baseline_images/test_image/figimage-1.pdf create mode 100644 lib/matplotlib/tests/baseline_images/test_image/figimage-1.svg diff --git a/lib/matplotlib/tests/baseline_images/test_image/figimage-0.pdf b/lib/matplotlib/tests/baseline_images/test_image/figimage-0.pdf new file mode 100644 index 0000000000000000000000000000000000000000..53b98a11d5cb5531e6ba2d7ce60fef4be3616de0 GIT binary patch literal 59614 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πWGPgN+uMdUwPU-^i=0<`!w% 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 i + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_image/figimage-1.pdf b/lib/matplotlib/tests/baseline_images/test_image/figimage-1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..53b98a11d5cb5531e6ba2d7ce60fef4be3616de0 GIT binary patch literal 59614 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πWGPgN+uMdUwPU-^i=0<`!w% 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 i + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index a4aee30557e6..4ff1e9d21f22 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -92,7 +92,7 @@ def test_figimage(): @image_comparison(baseline_images=['figimage-1'], extensions=['png','svg','pdf']) -def test_figimage(): +def test_figimage2(): 'test the figimage method' for suppressComposite in [True]: From a473231b5afb4ec32c23d834ac86ce5ecd96856b Mon Sep 17 00:00:00 2001 From: Eric Firing Date: Tue, 19 Sep 2017 21:01:18 -1000 Subject: [PATCH 058/433] PEP-8: add space after comma in test --- lib/matplotlib/tests/test_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 108eea4b7068..25a4fff43edc 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4934,7 +4934,7 @@ def test_pandas_pcolormesh(): time = pd.date_range('2000-01-01', periods=10) depth = np.arange(20) - data = np.random.rand(20,10) + data = np.random.rand(20, 10) fig, ax = plt.subplots() ax.pcolormesh(time, depth, data) From 90dcfe269e03ab21c8d100f44eec6ce192abd895 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 20 Sep 2017 12:35:37 -0700 Subject: [PATCH 059/433] removed svg from tests and changed test names to match image names --- lib/matplotlib/axes/_base.py | 2 ++ .../baseline_images/test_image/figimage-0.svg | 29 ------------------- .../baseline_images/test_image/figimage-1.svg | 29 ------------------- lib/matplotlib/tests/test_image.py | 8 ++--- 4 files changed, 6 insertions(+), 62 deletions(-) delete mode 100644 lib/matplotlib/tests/baseline_images/test_image/figimage-0.svg delete mode 100644 lib/matplotlib/tests/baseline_images/test_image/figimage-1.svg diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index eeadfc87a1e0..aea011fdc828 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1356,6 +1356,7 @@ def apply_aspect(self, position=None): Use :meth:`_aspect` and :meth:`_adjustable` to modify the axes box or the view limits. """ + print('Apply Aspect') if position is None: position = self.get_position(original=True) @@ -1406,6 +1407,7 @@ def apply_aspect(self, position=None): pb = position.frozen() pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) self.set_position(pb1.anchored(self.get_anchor(), pb), 'active') + print('self.gwet_positions', self.get_position(), self._position) return # reset active to original in case it had been changed diff --git a/lib/matplotlib/tests/baseline_images/test_image/figimage-0.svg b/lib/matplotlib/tests/baseline_images/test_image/figimage-0.svg deleted file mode 100644 index d071a91cfa8c..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_image/figimage-0.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/baseline_images/test_image/figimage-1.svg b/lib/matplotlib/tests/baseline_images/test_image/figimage-1.svg deleted file mode 100644 index d071a91cfa8c..000000000000 --- a/lib/matplotlib/tests/baseline_images/test_image/figimage-1.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 4ff1e9d21f22..c070d8781c46 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -73,8 +73,8 @@ def test_interp_nearest_vs_none(): @image_comparison(baseline_images=['figimage-0'], - extensions=['png','svg','pdf']) -def test_figimage(): + extensions=['png','pdf']) +def test_figimage0(): 'test the figimage method' for suppressComposite in [False]: @@ -91,8 +91,8 @@ def test_figimage(): fig.figimage(img[::-1,::-1], xo=100, yo=100, origin='lower') @image_comparison(baseline_images=['figimage-1'], - extensions=['png','svg','pdf']) -def test_figimage2(): + extensions=['png','pdf']) +def test_figimage1(): 'test the figimage method' for suppressComposite in [True]: From f85a6c16fe03025682ab8223ef350443c973451f Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Wed, 20 Sep 2017 14:33:06 -0700 Subject: [PATCH 060/433] Shorten line, remove 2nd LooseVersion in comparrison --- lib/matplotlib/backends/wx_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/wx_compat.py b/lib/matplotlib/backends/wx_compat.py index bc2a0f746f0a..e25bd4aa9131 100644 --- a/lib/matplotlib/backends/wx_compat.py +++ b/lib/matplotlib/backends/wx_compat.py @@ -153,7 +153,7 @@ def _AddTool(parent, wx_ids, text, bmp, tooltip_text): else: add_tool = parent.DoAddTool - if not is_phoenix or LooseVersion(wx.VERSION_STRING) >= LooseVersion("4.0.0b2"): + if not is_phoenix or LooseVersion(wx.VERSION_STRING) >= "4.0.0b2": # NOTE: when support for Phoenix prior to 4.0.0b2 is dropped then # all that is needed is this clause, and the if and else clause can # be removed. From 1a23a5ac2f47a8cdefaf74c0791a8c8973569a6f Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 20 Sep 2017 16:19:45 -0700 Subject: [PATCH 061/433] Fixed mis-edited _base.py --- lib/matplotlib/axes/_base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index aea011fdc828..eeadfc87a1e0 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1356,7 +1356,6 @@ def apply_aspect(self, position=None): Use :meth:`_aspect` and :meth:`_adjustable` to modify the axes box or the view limits. """ - print('Apply Aspect') if position is None: position = self.get_position(original=True) @@ -1407,7 +1406,6 @@ def apply_aspect(self, position=None): pb = position.frozen() pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) self.set_position(pb1.anchored(self.get_anchor(), pb), 'active') - print('self.gwet_positions', self.get_position(), self._position) return # reset active to original in case it had been changed From 30f78978e64df6a1d0c86510a9950008dffd9e29 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 20 Sep 2017 18:04:45 -0700 Subject: [PATCH 062/433] cleaned up duplicate code in test_image --- lib/matplotlib/tests/test_image.py | 42 +++++++++++++----------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index c070d8781c46..68a22894ec7b 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -72,41 +72,35 @@ def test_interp_nearest_vs_none(): ax2.set_title('interpolation nearest') +def do_figimage(suppressComposite): + """ Helper for the next two tests """ + fig = plt.figure(figsize=(2,2), dpi=100) + fig.suppressComposite = suppressComposite + x,y = np.ix_(np.arange(100.0)/100.0, np.arange(100.0)/100.0) + z = np.sin(x**2 + y**2 - x*y) + c = np.sin(20*x**2 + 50*y**2) + img = z + c/5 + + fig.figimage(img, xo=0, yo=0, origin='lower') + fig.figimage(img[::-1,:], xo=0, yo=100, origin='lower') + fig.figimage(img[:,::-1], xo=100, yo=0, origin='lower') + fig.figimage(img[::-1,::-1], xo=100, yo=100, origin='lower') + @image_comparison(baseline_images=['figimage-0'], extensions=['png','pdf']) def test_figimage0(): 'test the figimage method' - for suppressComposite in [False]: - fig = plt.figure(figsize=(2,2), dpi=100) - fig.suppressComposite = suppressComposite - x,y = np.ix_(np.arange(100.0)/100.0, np.arange(100.0)/100.0) - z = np.sin(x**2 + y**2 - x*y) - c = np.sin(20*x**2 + 50*y**2) - img = z + c/5 + suppressComposite = False + do_figimage(suppressComposite) - fig.figimage(img, xo=0, yo=0, origin='lower') - fig.figimage(img[::-1,:], xo=0, yo=100, origin='lower') - fig.figimage(img[:,::-1], xo=100, yo=0, origin='lower') - fig.figimage(img[::-1,::-1], xo=100, yo=100, origin='lower') @image_comparison(baseline_images=['figimage-1'], extensions=['png','pdf']) def test_figimage1(): 'test the figimage method' - - for suppressComposite in [True]: - fig = plt.figure(figsize=(2,2), dpi=100) - fig.suppressComposite = suppressComposite - x,y = np.ix_(np.arange(100.0)/100.0, np.arange(100.0)/100.0) - z = np.sin(x**2 + y**2 - x*y) - c = np.sin(20*x**2 + 50*y**2) - img = z + c/5 - - fig.figimage(img, xo=0, yo=0, origin='lower') - fig.figimage(img[::-1,:], xo=0, yo=100, origin='lower') - fig.figimage(img[:,::-1], xo=100, yo=0, origin='lower') - fig.figimage(img[::-1,::-1], xo=100, yo=100, origin='lower') + suppressComposite = True + do_figimage(suppressComposite) def test_image_python_io(): From 7a3747f0cc4e3ceb5ae231be731cea5b5ffc30df Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 21 Sep 2017 18:37:58 -0700 Subject: [PATCH 063/433] Mark svg files as binary/generated in gitattributes. This will suppress them from the github UI diffs (and line counts), see https://github.com/github/linguist#generated-code. --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 7d11db1dd140..64e5d9716c35 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ * text=auto +*.svg binary +*.svg linguist-language=true lib/matplotlib/_version.py export-subst From ffbb57ef238d6f9b4cc769bc70297e0ceee019bd Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 21 Sep 2017 21:59:11 -0400 Subject: [PATCH 064/433] STY: remove extra line --- lib/matplotlib/backends/wx_compat.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/matplotlib/backends/wx_compat.py b/lib/matplotlib/backends/wx_compat.py index e25bd4aa9131..137af577d881 100644 --- a/lib/matplotlib/backends/wx_compat.py +++ b/lib/matplotlib/backends/wx_compat.py @@ -172,4 +172,3 @@ def _AddTool(parent, wx_ids, text, bmp, tooltip_text): kind=kind) return add_tool(wx_ids[text], **kwargs) - From a6f38ab0338481a6b2ff0ec6bd523efb88545235 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 20 Sep 2017 12:01:45 -0700 Subject: [PATCH 065/433] Update wxpython wheel url for travis. The old url only points to sdists now, which caused travis to try building wxpython itself, with no success. Wheels are now at the new url. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cd19f60b7de1..8cd801b42ce3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -124,7 +124,7 @@ install: echo 'PyQt5 is available' || echo 'PyQt5 is not available' pip install -U --pre \ - -f https://wxpython.org/Phoenix/release-extras/linux/gtk3/ubuntu-14.04 \ + -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-14.04 \ wxPython && python -c 'import wx' && echo 'wxPython is available' || From 918649aeadb654d273341aefdcfb7b9dcea93a21 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 24 Sep 2017 11:16:41 -0700 Subject: [PATCH 066/433] Small index change --- doc/_templates/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/_templates/index.html b/doc/_templates/index.html index 298a7415c07e..e1ac6f7a529d 100644 --- a/doc/_templates/index.html +++ b/doc/_templates/index.html @@ -93,6 +93,9 @@

Documentation

This is the documentation for Matplotlib version {{ version }}. + To get started, read the + User's Guide. +

', '', content, - flags=re.MULTILINE | re.DOTALL) - fh.write(content) - fh.truncate() - - -def latex(): - """Build Sphinx 'latex' target. """ - check_build() - # figs() - if sys.platform != 'win32': - # LaTeX format. - if subprocess.call( - [sys.executable] - + '-msphinx -b latex -d build/doctrees . build/latex'.split()): - raise SystemExit("Building LaTeX failed.") - - # Produce pdf. - # Call the makefile produced by sphinx... - if subprocess.call("make", cwd="build/latex"): - raise SystemExit("Rendering LaTeX failed with.") - else: - print('latex build has not been tested on windows') - - -def texinfo(): - """Build Sphinx 'texinfo' target. """ - check_build() - # figs() - if sys.platform != 'win32': - # Texinfo format. - if subprocess.call( - [sys.executable] - + '-msphinx -b texinfo -d build/doctrees . build/texinfo'.split()): - raise SystemExit("Building Texinfo failed.") - - # Produce info file. - # Call the makefile produced by sphinx... - if subprocess.call("make", cwd="build/texinfo"): - raise SystemExit("Rendering Texinfo failed with.") - else: - print('texinfo build has not been tested on windows') - - -def clean(): - """Remove generated files. """ - shutil.rmtree("build", ignore_errors=True) - shutil.rmtree("tutorials", ignore_errors=True) - shutil.rmtree("api/_as_gen", ignore_errors=True) - for pattern in ['_static/matplotlibrc', - '_templates/gallery.html', - 'users/installing.rst']: - for filename in glob.glob(pattern): - if os.path.exists(filename): - os.remove(filename) - - -def build_all(): - """Build Sphinx 'html' and 'latex' target. """ - # figs() - html() - latex() - - -funcd = { - 'html': html, - 'htmlhelp': htmlhelp, - 'latex': latex, - 'texinfo': texinfo, - 'clean': clean, - 'all': build_all, - 'doctest': doctest, - 'linkcheck': linkcheck, - } - - -# Change directory to the one containing this file -current_dir = os.getcwd() -os.chdir(os.path.dirname(os.path.join(current_dir, __file__))) -copy_if_out_of_date('../INSTALL.rst', 'users/installing.rst') - -parser = argparse.ArgumentParser(description='Build matplotlib docs') -parser.add_argument( - "cmd", nargs="*", - help="Command(s) to execute. Valid commands: {}." - .format(", ".join(sorted(funcd)))) -parser.add_argument( - "--small", action="store_true", default=False, - help="Smaller docs with only low resolution PNG figures.") -parser.add_argument( - "--allowsphinxwarnings", action="store_true", default=False, - help="Don't turn Sphinx warnings into errors.") -parser.add_argument( - "-n", type=int, default=1, - help="Number of parallel workers to use.") - -args = parser.parse_args() -small_docs = args.small -warnings_as_errors = not args.allowsphinxwarnings -n_proc = args.n - -_valid_commands = "Valid commnds: {}".format(", ".join(sorted(funcd))) -if args.cmd: - for command in args.cmd: - func = funcd.get(command) - if func is None: - raise SystemExit("Do not know how to handle {}. {}" - .format(command, _valid_commands)) - func() -else: - raise SystemExit(_valid_commands) -os.chdir(current_dir) diff --git a/doc/users/installing.rst b/doc/users/installing.rst new file mode 100644 index 000000000000..545ae4fa153e --- /dev/null +++ b/doc/users/installing.rst @@ -0,0 +1 @@ +.. include:: ../../INSTALL.rst diff --git a/tutorials/introductory/customizing.py b/tutorials/introductory/customizing.py index 03b38b59ac09..0be0daf16d6a 100644 --- a/tutorials/introductory/customizing.py +++ b/tutorials/introductory/customizing.py @@ -180,11 +180,7 @@ # A sample matplotlibrc file # ~~~~~~~~~~~~~~~~~~~~~~~~~~ # -# .. htmlonly:: -# -# `(download) <../../_static/matplotlibrc>`__ -# -# .. literalinclude:: ../../_static/matplotlibrc +# .. literalinclude:: ../../../matplotlibrc.template # # # .. _matplotlibrc: http://matplotlib.org/users/customizing.html From 0ea465a4a78eb40623189b4e765f5dd7a663595d Mon Sep 17 00:00:00 2001 From: mattip Date: Fri, 1 Dec 2017 14:42:03 +0200 Subject: [PATCH 415/433] make SubplotTool into a modal dialog, keep ref to SubplotTool --- lib/matplotlib/backends/backend_tkagg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_tkagg.py b/lib/matplotlib/backends/backend_tkagg.py index 591183db1a97..85d32067f4e2 100644 --- a/lib/matplotlib/backends/backend_tkagg.py +++ b/lib/matplotlib/backends/backend_tkagg.py @@ -735,12 +735,13 @@ def _init_toolbar(self): def configure_subplots(self): toolfig = Figure(figsize=(6,3)) - window = Tk.Tk() + window = Tk.Toplevel() canvas = FigureCanvasTkAgg(toolfig, master=window) toolfig.subplots_adjust(top=0.9) - tool = SubplotTool(self.canvas.figure, toolfig) + canvas.tool = SubplotTool(self.canvas.figure, toolfig) canvas.show() canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1) + window.grab_set() def save_figure(self, *args): from six.moves import tkinter_tkfiledialog, tkinter_messagebox From 35adb8171e40c00b07d3143beed431d60b1dcac7 Mon Sep 17 00:00:00 2001 From: Dakota Blair Date: Fri, 1 Dec 2017 16:25:01 +0000 Subject: [PATCH 416/433] DOC: Updates multiprocessing example. --- ...multiprocess_sgskip.py => multiprocess.py} | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) rename examples/misc/{multiprocess_sgskip.py => multiprocess.py} (60%) diff --git a/examples/misc/multiprocess_sgskip.py b/examples/misc/multiprocess.py similarity index 60% rename from examples/misc/multiprocess_sgskip.py rename to examples/misc/multiprocess.py index eace2e05fd88..9a36674ceaab 100644 --- a/examples/misc/multiprocess_sgskip.py +++ b/examples/misc/multiprocess.py @@ -3,27 +3,37 @@ Multiprocess ============ -Demo of using multiprocessing for generating data in one process and plotting -in another. +Demo of using multiprocessing for generating data in one process and +plotting in another. Written by Robert Cimrman """ - from __future__ import print_function -from six.moves import input import time -from multiprocessing import Process, Pipe import numpy as np -import matplotlib -matplotlib.use('GtkAgg') +from multiprocessing import Process, Pipe + +# This example will likely not work with the native OSX backend. +# Uncomment the following lines to use the qt5 backend instead. +# +# import matplotlib +# matplotlib.use('qt5Agg') + import matplotlib.pyplot as plt -import gobject # Fixing random state for reproducibility np.random.seed(19680801) +############################################################################### +# +# Processing Class +# ================ +# +# This class plots data it recieves from a pipe. +# + class ProcessPlotter(object): def __init__(self): @@ -55,18 +65,36 @@ def __call__(self, pipe): self.pipe = pipe self.fig, self.ax = plt.subplots() - self.gid = gobject.timeout_add(1000, self.poll_draw()) + timer = self.fig.canvas.new_timer(interval=1000) + timer.add_callback(self.poll_draw()) + timer.start() print('...done') plt.show() +############################################################################### +# +# Plotting class +# ============== +# +# This class uses multiprocessing to spawn a process to run code from the +# class above. When initialized, it creates a pipe and an instance of +# ``ProcessPlotter`` which will be run in a separate process. +# +# When run from the command line, the parent process sends data to the spawned +# process which is then plotted via the callback function specified in +# ``ProcessPlotter:__call__``. +# + class NBPlot(object): def __init__(self): self.plot_pipe, plotter_pipe = Pipe() self.plotter = ProcessPlotter() - self.plot_process = Process(target=self.plotter, - args=(plotter_pipe,)) + self.plot_process = Process( + target=self.plotter, + args=(plotter_pipe,) + ) self.plot_process.daemon = True self.plot_process.start() @@ -84,8 +112,8 @@ def main(): for ii in range(10): pl.plot() time.sleep(0.5) - input('press Enter...') pl.plot(finished=True) + if __name__ == '__main__': main() From 421831dc1671eaf0d04159571b504c6ac27b7be8 Mon Sep 17 00:00:00 2001 From: Dakota Blair Date: Fri, 1 Dec 2017 16:40:12 +0000 Subject: [PATCH 417/433] Restore multiprocessing example filename to skip doc generation per conversation with tacaswell. --- examples/misc/{multiprocess.py => multiprocess_sgskip.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/misc/{multiprocess.py => multiprocess_sgskip.py} (100%) diff --git a/examples/misc/multiprocess.py b/examples/misc/multiprocess_sgskip.py similarity index 100% rename from examples/misc/multiprocess.py rename to examples/misc/multiprocess_sgskip.py From 23664c53c207b489874b2aa330208ab2ae8e94cc Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 1 Dec 2017 11:20:46 -0800 Subject: [PATCH 418/433] Deprecate unused ContourLabeler.get_real_label_width. --- doc/api/api_changes/2017-12-01-AL.rst | 4 ++++ lib/matplotlib/contour.py | 1 + 2 files changed, 5 insertions(+) create mode 100644 doc/api/api_changes/2017-12-01-AL.rst diff --git a/doc/api/api_changes/2017-12-01-AL.rst b/doc/api/api_changes/2017-12-01-AL.rst new file mode 100644 index 000000000000..ff86ce58a54f --- /dev/null +++ b/doc/api/api_changes/2017-12-01-AL.rst @@ -0,0 +1,4 @@ +Deprecation of unused methods +````````````````````````````` + +The unused ``ContourLabeler.get_real_label_width`` method is deprecated. diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index ecbde9ae91b5..a4e08951a3a8 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -289,6 +289,7 @@ def get_label_width(self, lev, fmt, fsize): return lw + @cbook.deprecated("2.2") def get_real_label_width(self, lev, fmt, fsize): """ This computes actual onscreen label width. From adb2f8aa2b25d4ff4ee13349e365b4bf8836a936 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 25 Nov 2017 22:15:43 -0800 Subject: [PATCH 419/433] less_simple_linear_interpolation can be replaced by np.interp. Also, in contour (the only place that used it), -1 (instead of nan) is a perfectly reasonable marker for out of bounds which avoids having to dance with the errstate. --- doc/api/api_changes/2017-11-31-AL.rst | 5 ++ lib/matplotlib/contour.py | 61 ++++++++---------------- lib/matplotlib/mlab.py | 1 + lib/matplotlib/tests/test_patheffects.py | 2 +- 4 files changed, 27 insertions(+), 42 deletions(-) create mode 100644 doc/api/api_changes/2017-11-31-AL.rst diff --git a/doc/api/api_changes/2017-11-31-AL.rst b/doc/api/api_changes/2017-11-31-AL.rst new file mode 100644 index 000000000000..8efa9a95c1df --- /dev/null +++ b/doc/api/api_changes/2017-11-31-AL.rst @@ -0,0 +1,5 @@ +Deprecations +```````````` + +The now unused ``mlab.less_simple_linear_interpolation`` function is +deprecated. diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index ecbde9ae91b5..f00b352e2077 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -417,25 +417,15 @@ def calc_label_rot_and_inline(self, slc, ind, lw, lc=None, spacing=5): else: dp = np.zeros_like(xi) - ll = mlab.less_simple_linear_interpolation(pl, slc, dp + xi, - extrap=True) - - # get vector in pixel space coordinates from one point to other - dd = np.diff(ll, axis=0).ravel() - - # Get angle of vector - must be calculated in pixel space for - # text rotation to work correctly - if np.all(dd == 0): # Must deal with case of zero length label - rotation = 0.0 - else: - rotation = np.rad2deg(np.arctan2(dd[1], dd[0])) + # Get angle of vector between the two ends of the label - must be + # calculated in pixel space for text rotation to work correctly. + (dx,), (dy,) = (np.diff(np.interp(dp + xi, pl, slc_col)) + for slc_col in slc.T) + rotation = np.rad2deg(np.arctan2(dy, dx)) if self.rightside_up: # Fix angle so text is never upside-down - if rotation > 90: - rotation = rotation - 180.0 - if rotation < -90: - rotation = 180.0 + rotation + rotation = (rotation + 90) % 180 - 90 # Break contour if desired nlc = [] @@ -443,37 +433,26 @@ def calc_label_rot_and_inline(self, slc, ind, lw, lc=None, spacing=5): # Expand range by spacing xi = dp + xi + np.array([-spacing, spacing]) - # Get indices near points of interest - I = mlab.less_simple_linear_interpolation( - pl, np.arange(len(pl)), xi, extrap=False) - - # If those indices aren't beyond contour edge, find x,y - if (not np.isnan(I[0])) and int(I[0]) != I[0]: - xy1 = mlab.less_simple_linear_interpolation( - pl, lc, [xi[0]]) - - if (not np.isnan(I[1])) and int(I[1]) != I[1]: - xy2 = mlab.less_simple_linear_interpolation( - pl, lc, [xi[1]]) - - # Round to integer values but keep as float - # To allow check against nan below - # Ignore nans here to avoid throwing an error on Appveyor build - # (can possibly be removed when build uses numpy 1.13) - with np.errstate(invalid='ignore'): - I = [np.floor(I[0]), np.ceil(I[1])] + # Get (integer) indices near points of interest; use -1 as marker + # for out of bounds. + I = np.interp(xi, pl, np.arange(len(pl)), left=-1, right=-1) + I = [np.floor(I[0]).astype(int), np.ceil(I[1]).astype(int)] + if I[0] != -1: + xy1 = [np.interp(xi[0], pl, lc_col) for lc_col in lc.T] + if I[1] != -1: + xy2 = [np.interp(xi[1], pl, lc_col) for lc_col in lc.T] # Actually break contours if closed: # This will remove contour if shorter than label - if np.all(~np.isnan(I)): - nlc.append(np.r_[xy2, lc[int(I[1]):int(I[0]) + 1], xy1]) + if np.all(I != -1): + nlc.append(np.row_stack([xy2, lc[I[1]:I[0]+1], xy1])) else: # These will remove pieces of contour if they have length zero - if not np.isnan(I[0]): - nlc.append(np.r_[lc[:int(I[0]) + 1], xy1]) - if not np.isnan(I[1]): - nlc.append(np.r_[xy2, lc[int(I[1]):]]) + if I[0] != -1: + nlc.append(np.row_stack([lc[:I[0]+1], xy1])) + if I[1] != -1: + nlc.append(np.row_stack([xy2, lc[I[1]:]])) # The current implementation removes contours completely # covered by labels. Uncomment line below to keep diff --git a/lib/matplotlib/mlab.py b/lib/matplotlib/mlab.py index 3664e869814e..65a4885de923 100644 --- a/lib/matplotlib/mlab.py +++ b/lib/matplotlib/mlab.py @@ -3404,6 +3404,7 @@ def griddata(x, y, z, xi, yi, interp='nn'): ################################################## # Linear interpolation algorithms ################################################## +@cbook.deprecated("2.2", alternative="np.interp") def less_simple_linear_interpolation(x, y, xi, extrap=False): """ This function provides simple (but somewhat less so than diff --git a/lib/matplotlib/tests/test_patheffects.py b/lib/matplotlib/tests/test_patheffects.py index 5b33180af524..6a8aadaea997 100644 --- a/lib/matplotlib/tests/test_patheffects.py +++ b/lib/matplotlib/tests/test_patheffects.py @@ -122,7 +122,7 @@ def test_SimplePatchShadow_offset(): assert pe._offset == (4, 5) -@image_comparison(baseline_images=['collection'], tol=0.015) +@image_comparison(baseline_images=['collection'], tol=0.02) def test_collection(): x, y = np.meshgrid(np.linspace(0, 10, 150), np.linspace(-5, 5, 100)) data = np.sin(x) + np.cos(y) From df4e62300edda78b05782aee6eb036f3d7f32457 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 1 Dec 2017 11:50:30 -0800 Subject: [PATCH 420/433] Some more cleanups. --- lib/matplotlib/contour.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index a4e08951a3a8..afba7812a224 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -337,8 +337,7 @@ def get_text(self, lev, fmt): def locate_label(self, linecontour, labelwidth): """ - Find a good place to plot a label (relatively flat - part of the contour). + Find good place to draw a label (relatively flat part of the contour). """ # Number of contour points @@ -355,16 +354,15 @@ def locate_label(self, linecontour, labelwidth): XX = np.resize(linecontour[:, 0], (xsize, ysize)) YY = np.resize(linecontour[:, 1], (xsize, ysize)) # I might have fouled up the following: - yfirst = YY[:, 0].reshape(xsize, 1) - ylast = YY[:, -1].reshape(xsize, 1) - xfirst = XX[:, 0].reshape(xsize, 1) - xlast = XX[:, -1].reshape(xsize, 1) + yfirst = YY[:, :1] + ylast = YY[:, -1:] + xfirst = XX[:, :1] + xlast = XX[:, -1:] s = (yfirst - YY) * (xlast - xfirst) - (xfirst - XX) * (ylast - yfirst) - L = np.sqrt((xlast - xfirst) ** 2 + (ylast - yfirst) ** 2).ravel() + L = np.hypot(xlast - xfirst, ylast - yfirst) # Ignore warning that divide by zero throws, as this is a valid option with np.errstate(divide='ignore', invalid='ignore'): - dist = np.add.reduce([(abs(s)[i] / L[i]) for i in range(xsize)], - -1) + dist = np.sum(np.abs(s) / L, axis=-1) x, y, ind = self.get_label_coords(dist, XX, YY, ysize, labelwidth) # There must be a more efficient way... From f872c37f1699d796c4bc535d6f4f5c7c32711403 Mon Sep 17 00:00:00 2001 From: "Adrien F. Vincent" Date: Fri, 1 Dec 2017 12:26:32 -0800 Subject: [PATCH 421/433] clean and enhance annotated barchart example --- examples/api/barchart.py | 49 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/examples/api/barchart.py b/examples/api/barchart.py index f5111d945066..091a5e4867f9 100644 --- a/examples/api/barchart.py +++ b/examples/api/barchart.py @@ -3,45 +3,50 @@ Barchart ======== -A bar plot with errorbars and height labels on individual bars +A bar plot with errorbars and height labels on individual bars. """ import numpy as np import matplotlib.pyplot as plt -N = 5 -men_means = (20, 35, 30, 35, 27) -men_std = (2, 3, 4, 1, 2) +men_means, men_std = (20, 35, 30, 35, 27), (2, 3, 4, 1, 2) +women_means, women_std = (25, 32, 34, 20, 25), (3, 5, 2, 3, 3) -ind = np.arange(N) # the x locations for the groups -width = 0.35 # the width of the bars +ind = np.arange(len(men_means)) # the x locations for the groups +width = 0.35 # the width of the bars fig, ax = plt.subplots() -rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std) +rects1 = ax.bar(ind - width/2, men_means, width, yerr=men_std, + color='SkyBlue', label='Men') +rects2 = ax.bar(ind + width/2, women_means, width, yerr=women_std, + color='IndianRed', label='Women') -women_means = (25, 32, 34, 20, 25) -women_std = (3, 5, 2, 3, 3) -rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std) - -# add some text for labels, title and axes ticks +# Add some text for labels, title and custom x-axis tick labels, etc. ax.set_ylabel('Scores') ax.set_title('Scores by group and gender') -ax.set_xticks(ind + width / 2) +ax.set_xticks(ind) ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5')) - -ax.legend((rects1[0], rects2[0]), ('Men', 'Women')) +ax.legend() -def autolabel(rects): +def autolabel(rects, xpos='center'): """ - Attach a text label above each bar displaying its height + Attach a text label above each bar in *rects*, displaying its height. + + *xpos* indicates which side to place the text w.r.t. the center of + the bar. It can be one of the following {'center', 'right', 'left'}. """ + + xpos = xpos.lower() # normalize the case of the parameter + ha = {'center': 'center', 'right': 'left', 'left': 'right'} + offset = {'center': 0.5, 'right': 0.57, 'left': 0.43} # x_txt = x + w*off + for rect in rects: height = rect.get_height() - ax.text(rect.get_x() + rect.get_width()/2., 1.05*height, - '%d' % int(height), - ha='center', va='bottom') + ax.text(rect.get_x() + rect.get_width()*offset[xpos], 1.01*height, + '{}'.format(height), ha=ha[xpos], va='bottom') + -autolabel(rects1) -autolabel(rects2) +autolabel(rects1, "left") +autolabel(rects2, "right") plt.show() From d257c135d1f0230d20d60bc6cf3c6f206813919a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 2 Dec 2017 13:42:16 -0800 Subject: [PATCH 422/433] Deprecate unused FigureManagerBase.show_popup. --- doc/api/api_changes/2017-12-02-AL.rst | 3 +++ doc/devel/MEP/MEP27.rst | 3 --- lib/matplotlib/backend_bases.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 doc/api/api_changes/2017-12-02-AL.rst diff --git a/doc/api/api_changes/2017-12-02-AL.rst b/doc/api/api_changes/2017-12-02-AL.rst new file mode 100644 index 000000000000..7a7b5d2ef49e --- /dev/null +++ b/doc/api/api_changes/2017-12-02-AL.rst @@ -0,0 +1,3 @@ +Deprecations +```````````` +The unused ``FigureManagerBase.show_popup`` method is deprecated. diff --git a/doc/devel/MEP/MEP27.rst b/doc/devel/MEP/MEP27.rst index 57b0540a4c91..63592cf475a3 100644 --- a/doc/devel/MEP/MEP27.rst +++ b/doc/devel/MEP/MEP27.rst @@ -114,9 +114,6 @@ The description of this MEP gives us most of the solution: +--------------------------------------+------------------------------+---------------------+--------------------------------+ |key_press |key_press | | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ -|show_popup |show_poup | |Not used anywhere in mpl, and | -| | | |does nothing. | -+--------------------------------------+------------------------------+---------------------+--------------------------------+ |get_window_title | |get_window_title | | +--------------------------------------+------------------------------+---------------------+--------------------------------+ |set_window_title | |set_window_title | | diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 25afd45238bd..37757620c2e5 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2696,6 +2696,7 @@ def key_press(self, event): if rcParams['toolbar'] != 'toolmanager': key_press_handler(event, self.canvas, self.canvas.toolbar) + @cbook.deprecated("2.2") def show_popup(self, msg): """Display message in a popup -- GUI only.""" From b4b1f64275f26670056f23ef0c4e484ec66cadff Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 2 Dec 2017 15:20:11 -0800 Subject: [PATCH 423/433] Default to -W. --- .circleci/config.yml | 2 +- doc/Makefile | 2 +- doc/devel/documenting_mpl.rst | 21 ++++++++++++++------- doc/make.bat | 6 ++++-- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 59633e3b1a67..aeaecc820647 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,7 +68,7 @@ mpl-run: &mpl-install doc-run: &doc-build name: Build documentation - command: make O=-W html + command: make html working_directory: doc doc-bundle-run: &doc-bundle diff --git a/doc/Makefile b/doc/Makefile index d4a3b055b466..da3e35fdd41f 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = +SPHINXOPTS = -W SPHINXBUILD = python -msphinx SPHINXPROJ = matplotlib SOURCEDIR = . diff --git a/doc/devel/documenting_mpl.rst b/doc/devel/documenting_mpl.rst index 6878ec3614b3..54b0e608fcd3 100644 --- a/doc/devel/documenting_mpl.rst +++ b/doc/devel/documenting_mpl.rst @@ -87,17 +87,24 @@ Other useful invocations include # Build pdf docs. make latexpdf -You can build the documentation with several options: +The ``SPHINXOPTS`` variable is set to ``-W`` by default to turn warnings into +errors. To unset it, use -* ``make O=-W html`` turns Sphinx warnings into errors. The continuous - integration script uses this option. -* ``make O=-j4 html`` (for example) runs a parallel build with 4 processes. +.. code-block:: sh + + make SPHINXOPTS= html + +You can use the ``O`` variable to set additional options: + +* ``make O=-j4 html`` runs a parallel build with 4 processes. * ``make O=-Dplot_formats=png:100 html`` saves figures in low resolution. * ``make O=-Dplot_gallery=0 html`` skips the gallery build. -Multiple options can be combined using e.g. ``make O='-W -j4 ...' html``. On -Windows, the option needs to be set as the ``SPHINXOPTS`` environment -variable, e.g. ``set SPHINXOPTS=-W -j4 & make html``. +Multiple options can be combined using e.g. ``make O='-j4 -Dplot_gallery=0' +html``. + +On Windows, options needs to be set as environment variables, e.g. ``set O=-W +-j4 & make html``. Writing new documentation ========================= diff --git a/doc/make.bat b/doc/make.bat index b2765b88a271..042c9ef3543b 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -10,6 +10,8 @@ if "%SPHINXBUILD%" == "" ( set SOURCEDIR=. set BUILDDIR=build set SPHINXPROJ=matplotlib +set SPHINXOPTS=-W +set O= %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( @@ -26,11 +28,11 @@ if errorlevel 9009 ( if "%1" == "" goto help -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd From 7c4cecd06685c580cbdc1757b6d07514b06625fe Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 2 Dec 2017 20:45:33 -0500 Subject: [PATCH 424/433] DOC: update deprecation note. Add context to where this method came from. --- doc/api/api_changes/2017-12-02-AL.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/api/api_changes/2017-12-02-AL.rst b/doc/api/api_changes/2017-12-02-AL.rst index 7a7b5d2ef49e..2fabbc598824 100644 --- a/doc/api/api_changes/2017-12-02-AL.rst +++ b/doc/api/api_changes/2017-12-02-AL.rst @@ -1,3 +1,6 @@ Deprecations ```````````` -The unused ``FigureManagerBase.show_popup`` method is deprecated. + +The unused ``FigureManagerBase.show_popup`` method is deprecated. This +introduced in e945059b327d42a99938b939a1be867fa023e7ba in 2005 but never built +out into any of the backends. From 5d86d45a5b10620c88eef80e3029131000a69573 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 2 Dec 2017 19:13:08 -0800 Subject: [PATCH 425/433] backend_agg cleanup. - Deprecate unused RendererAgg.debug variable. - Passing debug=False to _RendererAgg has no effect (PyRendererAgg_init actually ignores kwds, and doesn't do anything with debug if it is passed positionally either). - Moved a comment that was at the wrong line. - Wrapped lines to 79 chars; removed allowance in pep8 checker. --- lib/matplotlib/backends/backend_agg.py | 28 ++++++++++++++++---------- pytest.ini | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index 1d867fea8914..50a2c9d9a4d3 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -28,7 +28,7 @@ import numpy as np from collections import OrderedDict from math import radians, cos, sin -from matplotlib import rcParams, __version__ +from matplotlib import cbook, rcParams, __version__ from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, RendererBase, cursors) from matplotlib.figure import Figure @@ -68,7 +68,11 @@ class RendererAgg(RendererBase): The renderer handles all the drawing primitives using a graphics context instance that controls the colors/styles """ - debug=1 + + @property + @cbook.deprecated("2.2") + def debug(self): + return 1 # we want to cache the fonts at the class level so that when # multiple figures are created we can reuse them. This helps with @@ -79,16 +83,17 @@ class RendererAgg(RendererBase): # draw, and release it when it is done. This allows multiple # renderers to share the cached fonts, but only one figure can # draw at time and so the font cache is used by only one - # renderer at a time + # renderer at a time. lock = threading.RLock() + def __init__(self, width, height, dpi): RendererBase.__init__(self) self.dpi = dpi self.width = width self.height = height - self._renderer = _RendererAgg(int(width), int(height), dpi, debug=False) + self._renderer = _RendererAgg(int(width), int(height), dpi) self._filter_renderers = [] self._update_methods() @@ -158,12 +163,14 @@ def draw_path(self, gc, path, transform, rgbFace=None): try: self._renderer.draw_path(gc, p, transform, rgbFace) except OverflowError: - raise OverflowError("Exceeded cell block limit (set 'agg.path.chunksize' rcparam)") + raise OverflowError("Exceeded cell block limit (set " + "'agg.path.chunksize' rcparam)") else: try: self._renderer.draw_path(gc, path, transform, rgbFace) except OverflowError: - raise OverflowError("Exceeded cell block limit (set 'agg.path.chunksize' rcparam)") + raise OverflowError("Exceeded cell block limit (set " + "'agg.path.chunksize' rcparam)") def draw_mathtext(self, gc, x, y, s, prop, angle): @@ -232,8 +239,8 @@ def get_text_width_height_descent(self, s, prop, ismath): flags = get_hinting_flag() font = self._get_agg_font(prop) - font.set_text(s, 0.0, flags=flags) # the width and height of unrotated string - w, h = font.get_width_height() + font.set_text(s, 0.0, flags=flags) + w, h = font.get_width_height() # width and height of unrotated string d = font.get_descent() w /= 64.0 # convert from subpixels h /= 64.0 @@ -367,9 +374,8 @@ def post_processing(image, dpi): post_processing is plotted (using draw_image) on it. """ - # WARNING. - # For agg_filter to work, the rendere's method need - # to overridden in the class. See draw_markers, and draw_path_collections + # WARNING: For agg_filter to work, the renderer's method need to + # overridden in the class. See draw_markers and draw_path_collections. width, height = int(self.width), int(self.height) diff --git a/pytest.ini b/pytest.ini index 7214201f53dd..b9680a76bb11 100644 --- a/pytest.ini +++ b/pytest.ini @@ -23,7 +23,7 @@ pep8ignore = tools/subset.py E221 E231 E251 E261 E302 E501 E701 E703 matplotlib/backends/qt_editor/formlayout.py E301 E402 E501 - matplotlib/backends/backend_agg.py E225 E228 E231 E261 E301 E302 E303 E501 E701 + matplotlib/backends/backend_agg.py E225 E228 E231 E261 E301 E302 E303 E701 matplotlib/backends/backend_cairo.py E203 E211 E221 E231 E261 E272 E302 E303 E401 E402 E501 E701 E711 matplotlib/backends/backend_gdk.py E202 E203 E211 E221 E225 E231 E261 E302 E303 E402 E501 E702 E711 matplotlib/backends/backend_gtk.py E201 E202 E203 E211 E221 E222 E225 E231 E251 E261 E262 E301 E302 E303 E401 E402 E501 E701 E702 E703 From 98dfc0d3d5d2c3b59bd3a3dd06085cdd0a9a79f1 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 3 Dec 2017 01:20:06 -0800 Subject: [PATCH 426/433] Unify (parametrize) test_composite across backends. --- lib/matplotlib/tests/test_backend_ps.py | 24 ------------------------ lib/matplotlib/tests/test_backend_svg.py | 24 ------------------------ lib/matplotlib/tests/test_image.py | 20 ++++++++++++++++++++ 3 files changed, 20 insertions(+), 48 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 64fbd5dbe65c..17f58b6ee152 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -81,30 +81,6 @@ def test_savefig_to_stringio(format, use_log, rcParams): buffer.close() -def test_composite_image(): - # Test that figures can be saved with and without combining multiple images - # (on a single set of axes) into a single composite image. - X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1)) - Z = np.sin(Y ** 2) - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) - ax.set_xlim(0, 3) - ax.imshow(Z, extent=[0, 1, 0, 1]) - ax.imshow(Z[::-1], extent=[2, 3, 0, 1]) - plt.rcParams['image.composite_image'] = True - with io.BytesIO() as ps: - fig.savefig(ps, format="ps") - ps.seek(0) - buff = ps.read() - assert buff.count(six.b(' colorimage')) == 1 - plt.rcParams['image.composite_image'] = False - with io.BytesIO() as ps: - fig.savefig(ps, format="ps") - ps.seek(0) - buff = ps.read() - assert buff.count(six.b(' colorimage')) == 2 - - def test_patheffects(): with matplotlib.rc_context(): matplotlib.rcParams['path.effects'] = [ diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index cb031abd5ab8..055c220982a6 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -63,30 +63,6 @@ def test_noscale(): ax.imshow(Z, cmap='gray', interpolation='none') -def test_composite_images(): - #Test that figures can be saved with and without combining multiple images - #(on a single set of axes) into a single composite image. - X, Y = np.meshgrid(np.arange(-5, 5, 1), np.arange(-5, 5, 1)) - Z = np.sin(Y ** 2) - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) - ax.set_xlim(0, 3) - ax.imshow(Z, extent=[0, 1, 0, 1]) - ax.imshow(Z[::-1], extent=[2, 3, 0, 1]) - plt.rcParams['image.composite_image'] = True - with BytesIO() as svg: - fig.savefig(svg, format="svg") - svg.seek(0) - buff = svg.read() - assert buff.count(six.b(' Date: Tue, 22 Aug 2017 19:34:05 +0100 Subject: [PATCH 427/433] Use rectangle coords instead of width/height Fix up some incorrect bits in Rectangle Remember to update x1/y1 when setting x0/y0 Add methods to update x1/y1 --- lib/matplotlib/patches.py | 57 ++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 7dd29757e35b..90b591d5a740 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -643,7 +643,7 @@ class Rectangle(Patch): """ def __str__(self): - pars = self._x, self._y, self._width, self._height, self.angle + pars = self._x0, self._y0, self._width, self._height, self.angle fmt = "Rectangle(xy=(%g, %g), width=%g, height=%g, angle=%g)" return fmt % pars @@ -662,10 +662,15 @@ def __init__(self, xy, width, height, angle=0.0, **kwargs): Patch.__init__(self, **kwargs) - self._x = xy[0] - self._y = xy[1] + self._x0 = xy[0] + self._y0 = xy[1] + self._width = width self._height = height + + self._x1 = self._x0 + self._width + self._y1 = self._y0 + self._height + self.angle = float(angle) # Note: This cannot be calculated until this is added to an Axes self._rect_transform = transforms.IdentityTransform() @@ -682,31 +687,37 @@ def _update_patch_transform(self): makes it very important to call the accessor method and not directly access the transformation member variable. """ - x = self.convert_xunits(self._x) - y = self.convert_yunits(self._y) - width = self.convert_xunits(self._width) - height = self.convert_yunits(self._height) - bbox = transforms.Bbox.from_bounds(x, y, width, height) + x0 = self.convert_xunits(self._x0) + y0 = self.convert_yunits(self._y0) + x1 = self.convert_xunits(self._x1) + y1 = self.convert_yunits(self._y1) + bbox = transforms.Bbox.from_extents(x0, y0, x1, y1) rot_trans = transforms.Affine2D() - rot_trans.rotate_deg_around(x, y, self.angle) + rot_trans.rotate_deg_around(x0, y0, self.angle) self._rect_transform = transforms.BboxTransformTo(bbox) self._rect_transform += rot_trans + def _update_x1(self): + self._x1 = self._x0 + self._width + + def _update_y1(self): + self._y1 = self._y0 + self._height + def get_patch_transform(self): self._update_patch_transform() return self._rect_transform def get_x(self): "Return the left coord of the rectangle" - return self._x + return self._x0 def get_y(self): "Return the bottom coord of the rectangle" - return self._y + return self._y0 def get_xy(self): "Return the left and bottom coords of the rectangle" - return self._x, self._y + return self._x0, self._y0 def get_width(self): "Return the width of the rectangle" @@ -722,7 +733,8 @@ def set_x(self, x): ACCEPTS: float """ - self._x = x + self._x0 = x + self._update_x1() self.stale = True def set_y(self, y): @@ -731,7 +743,8 @@ def set_y(self, y): ACCEPTS: float """ - self._y = y + self._y0 = y + self._update_y1() self.stale = True def set_xy(self, xy): @@ -740,7 +753,9 @@ def set_xy(self, xy): ACCEPTS: 2-item sequence """ - self._x, self._y = xy + self._x0, self._y0 = xy + self._update_x1() + self._update_y1() self.stale = True def set_width(self, w): @@ -750,6 +765,7 @@ def set_width(self, w): ACCEPTS: float """ self._width = w + self._update_x1() self.stale = True def set_height(self, h): @@ -759,6 +775,7 @@ def set_height(self, h): ACCEPTS: float """ self._height = h + self._update_y1() self.stale = True def set_bounds(self, *args): @@ -771,15 +788,17 @@ def set_bounds(self, *args): l, b, w, h = args[0] else: l, b, w, h = args - self._x = l - self._y = b + self._x0 = l + self._y0 = b self._width = w self._height = h + self._update_x1() + self._update_y1() self.stale = True def get_bbox(self): - return transforms.Bbox.from_bounds(self._x, self._y, - self._width, self._height) + return transforms.Bbox.from_extents(self._x0, self._y0, + self._x1, self._y1) xy = property(get_xy, set_xy) From 8208ddc9d88df4e0730a8ada99e815498a8712e3 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Tue, 22 Aug 2017 20:02:59 +0100 Subject: [PATCH 428/433] Add timedelta rectangle test --- lib/matplotlib/tests/test_patches.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 7693148bef57..6fb53fc229b0 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -362,3 +362,15 @@ def test_connection_patch(): axesA=ax2, axesB=ax1, arrowstyle="->") ax2.add_artist(con) + + +def test_datetime_rectangle(): + # Check that creating a rectangle with timedeltas doesn't fail + from datetime import datetime, timedelta + + start = datetime(2017, 1, 1, 0, 0, 0) + delta = timedelta(seconds=16) + patch = mpatches.Rectangle((start, 0), delta, 1) + + fig, ax = plt.subplots() + ax.add_patch(patch) From 03fba6b4c784067636e59ac23a25fa4baaacafca Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Tue, 22 Aug 2017 15:44:09 -0400 Subject: [PATCH 429/433] Add a test to ensure that datetimes cannot be used as deltas. --- lib/matplotlib/tests/test_patches.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 6fb53fc229b0..2ea7af52e509 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -374,3 +374,16 @@ def test_datetime_rectangle(): fig, ax = plt.subplots() ax.add_patch(patch) + + +def test_datetime_datetime_fails(): + from datetime import datetime + + start = datetime(2017, 1, 1, 0, 0, 0) + dt_delta = datetime(1970, 1, 5) # Will be 5 days if units are done wrong + + with pytest.raises(TypeError): + mpatches.Rectangle((start, 0), dt_delta, 1) + + with pytest.raises(TypeError): + mpatches.Rectangle((0, start), 1, dt_delta) From f40607e20675560d1fe64e1e8f5c7038a4d2b325 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 25 Aug 2017 09:14:24 +0100 Subject: [PATCH 430/433] Convert units when returning Rect bbox --- lib/matplotlib/patches.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 90b591d5a740..c9b48ef1f750 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -687,10 +687,7 @@ def _update_patch_transform(self): makes it very important to call the accessor method and not directly access the transformation member variable. """ - x0 = self.convert_xunits(self._x0) - y0 = self.convert_yunits(self._y0) - x1 = self.convert_xunits(self._x1) - y1 = self.convert_yunits(self._y1) + x0, y0, x1, y1 = self._convert_units() bbox = transforms.Bbox.from_extents(x0, y0, x1, y1) rot_trans = transforms.Affine2D() rot_trans.rotate_deg_around(x0, y0, self.angle) @@ -703,6 +700,16 @@ def _update_x1(self): def _update_y1(self): self._y1 = self._y0 + self._height + def _convert_units(self): + ''' + Convert bounds of the rectangle + ''' + x0 = self.convert_xunits(self._x0) + y0 = self.convert_yunits(self._y0) + x1 = self.convert_xunits(self._x1) + y1 = self.convert_yunits(self._y1) + return x0, y0, x1, y1 + def get_patch_transform(self): self._update_patch_transform() return self._rect_transform @@ -720,7 +727,7 @@ def get_xy(self): return self._x0, self._y0 def get_width(self): - "Return the width of the rectangle" + "Return the width of the rectangle" return self._width def get_height(self): @@ -797,6 +804,7 @@ def set_bounds(self, *args): self.stale = True def get_bbox(self): + x0, y0, x1, y1 = self._convert_units() return transforms.Bbox.from_extents(self._x0, self._y0, self._x1, self._y1) From 8ee4da2103a9726445ffcfbfac890dff9032a7b3 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Fri, 25 Aug 2017 09:20:05 +0100 Subject: [PATCH 431/433] Clean docstring of Rectangle methods --- lib/matplotlib/patches.py | 43 ++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index c9b48ef1f750..e42280f6e29c 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -650,12 +650,21 @@ def __str__(self): @docstring.dedent_interpd def __init__(self, xy, width, height, angle=0.0, **kwargs): """ + Parameters + ---------- + xy: length-2 tuple + The bottom and left rectangle coordinates + width: + Rectangle width + height: + Rectangle height + angle: float, optional + rotation in degrees anti-clockwise about *xy* (default is 0.0) + fill: bool, optional + Whether to fill the rectangle (default is ``True``) - *angle* - rotation in degrees (anti-clockwise) - - *fill* is a boolean indicating whether to fill the rectangle - + Notes + ----- Valid kwargs are: %(Patch)s """ @@ -735,21 +744,13 @@ def get_height(self): return self._height def set_x(self, x): - """ - Set the left coord of the rectangle - - ACCEPTS: float - """ + "Set the left coord of the rectangle" self._x0 = x self._update_x1() self.stale = True def set_y(self, y): - """ - Set the bottom coord of the rectangle - - ACCEPTS: float - """ + "Set the bottom coord of the rectangle" self._y0 = y self._update_y1() self.stale = True @@ -766,21 +767,13 @@ def set_xy(self, xy): self.stale = True def set_width(self, w): - """ - Set the width rectangle - - ACCEPTS: float - """ + "Set the width of the rectangle" self._width = w self._update_x1() self.stale = True def set_height(self, h): - """ - Set the width rectangle - - ACCEPTS: float - """ + "Set the height of the rectangle" self._height = h self._update_y1() self.stale = True From 4886ea9150c5d16af80460c027195d28a214a665 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 3 Dec 2017 12:51:35 -0800 Subject: [PATCH 432/433] In unit/memleak, write to in-memory buffer instead of file. (Avoids cluttering the source directory.) --- unit/memleak.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unit/memleak.py b/unit/memleak.py index 3b56f996de6c..a57deaf41aed 100755 --- a/unit/memleak.py +++ b/unit/memleak.py @@ -3,6 +3,7 @@ from __future__ import print_function import gc +from io import BytesIO try: import tracemalloc @@ -110,7 +111,7 @@ def __call__(self): ax = fig.add_subplot(224) ax.pcolor(10 * np.random.rand(50, 50)) - fig.savefig('tmp', dpi=75) + fig.savefig(BytesIO(), dpi=75) plt.close(1) From 2fc62a4ecb146b4fc5b03c78be9a7ef8d4b2cab2 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 5 Dec 2017 02:20:27 +0100 Subject: [PATCH 433/433] cleanup delaxes() --- lib/matplotlib/figure.py | 8 +++++--- lib/matplotlib/pyplot.py | 15 +++++---------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 416b87d4a5f9..e3d86e3f8438 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -864,9 +864,11 @@ def set_frameon(self, b): self.frameon = b self.stale = True - def delaxes(self, a): - 'remove a from the figure and update the current axes' - self._axstack.remove(a) + def delaxes(self, ax): + """ + Remove the `~.Axes` *ax* from the figure and update the current axes. + """ + self._axstack.remove(ax) for func in self._axobservers: func(self) self.stale = True diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index df71cb2a7ac3..f33f25a1a394 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -919,19 +919,14 @@ def axes(*args, **kwargs): return a -def delaxes(*args): +def delaxes(ax=None): """ - Remove an axes from the current figure. If *ax* - doesn't exist, an error will be raised. - - ``delaxes()``: delete the current axes + Remove the given `Axes` *ax* from the current figure. If *ax* is *None*, + the current axes is removed. A KeyError is raised if the axes doesn't exist. """ - if not len(args): + if ax is None: ax = gca() - else: - ax = args[0] - ret = gcf().delaxes(ax) - return ret + gcf().delaxes(ax) def sca(ax):