From 126fb9c3eb820ecf08bca5efb8796eb0a55d9cd6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 3 Sep 2014 16:23:20 -0400 Subject: [PATCH 01/45] BUG : fix list comprehensions over class members Due to scoping fixes in py3k, list comprehensions over class level attributes during class definition does not work (see http://stackoverflow.com/questions/13905741/accessing-class-variables-from-a-list-comprehension-in-the-class-definition). Superficially Fixes #3436. There seem to be other issues --- lib/matplotlib/backends/backend_nbagg.py | 22 +++++++++---------- .../backends/backend_webagg_core.py | 20 ++++++++--------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index c69c558ea071..7676609a20b7 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -63,25 +63,25 @@ def connection_info(): str(len(pylab_helpers.Gcf._activeQue))) return '\n'.join(result) +# Note: Version 3.2 icons, not the later 4.0 ones. +# http://fontawesome.io/3.2.1/icons/ +_FONT_AWESOME_CLASSES = { + 'home': 'icon-home', + 'back': 'icon-arrow-left', + 'forward': 'icon-arrow-right', + 'zoom_to_rect': 'icon-check-empty', + 'move': 'icon-move', + None: None +} class NavigationIPy(NavigationToolbar2WebAgg): - # Note: Version 3.2 icons, not the later 4.0 ones. - # http://fontawesome.io/3.2.1/icons/ - _font_awesome_classes = { - 'home': 'icon-home', - 'back': 'icon-arrow-left', - 'forward': 'icon-arrow-right', - 'zoom_to_rect': 'icon-check-empty', - 'move': 'icon-move', - None: None - } # Use the standard toolbar items + download button toolitems = [(text, tooltip_text, _font_awesome_classes[image_file], name_of_method) for text, tooltip_text, image_file, name_of_method in NavigationToolbar2.toolitems - if image_file in _font_awesome_classes] + if image_file in _FONT_AWESOME_CLASSES] class FigureManagerNbAgg(FigureManagerWebAgg): diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index eee727dbc7c7..73429ca591dc 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -237,17 +237,17 @@ def stop_event_loop(self): stop_event_loop.__doc__ = \ backend_bases.FigureCanvasBase.stop_event_loop_default.__doc__ +_JQUERY_ICON_CLASSES = { + 'home': 'ui-icon ui-icon-home', + 'back': 'ui-icon ui-icon-circle-arrow-w', + 'forward': 'ui-icon ui-icon-circle-arrow-e', + 'zoom_to_rect': 'ui-icon ui-icon-search', + 'move': 'ui-icon ui-icon-arrow-4', + 'download': 'ui-icon ui-icon-disk', + None: None, +} class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2): - _jquery_icon_classes = { - 'home': 'ui-icon ui-icon-home', - 'back': 'ui-icon ui-icon-circle-arrow-w', - 'forward': 'ui-icon ui-icon-circle-arrow-e', - 'zoom_to_rect': 'ui-icon ui-icon-search', - 'move': 'ui-icon ui-icon-arrow-4', - 'download': 'ui-icon ui-icon-disk', - None: None, - } # Use the standard toolbar items + download button toolitems = [(text, tooltip_text, _jquery_icon_classes[image_file], @@ -255,7 +255,7 @@ class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2): for text, tooltip_text, image_file, name_of_method in (backend_bases.NavigationToolbar2.toolitems + (('Download', 'Download plot', 'download', 'download'),)) - if image_file in _jquery_icon_classes] + if image_file in _JQUERY_ICON_CLASSES] def _init_toolbar(self): self.message = '' From 32c11afbbb41d5ad60b124f2be43e5ef705287f2 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 4 Sep 2014 14:35:31 -0400 Subject: [PATCH 02/45] BUG : fix glaring syntax error --- lib/matplotlib/backends/backend_nbagg.py | 4 +++- lib/matplotlib/backends/backend_webagg_core.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 7676609a20b7..7ca9257ff389 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -63,6 +63,7 @@ def connection_info(): str(len(pylab_helpers.Gcf._activeQue))) return '\n'.join(result) + # Note: Version 3.2 icons, not the later 4.0 ones. # http://fontawesome.io/3.2.1/icons/ _FONT_AWESOME_CLASSES = { @@ -74,11 +75,12 @@ def connection_info(): None: None } + class NavigationIPy(NavigationToolbar2WebAgg): # Use the standard toolbar items + download button toolitems = [(text, tooltip_text, - _font_awesome_classes[image_file], name_of_method) + _FONT_AWESOME_CLASSES[image_file], name_of_method) for text, tooltip_text, image_file, name_of_method in NavigationToolbar2.toolitems if image_file in _FONT_AWESOME_CLASSES] diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 73429ca591dc..9caf28816d6b 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -237,6 +237,7 @@ def stop_event_loop(self): stop_event_loop.__doc__ = \ backend_bases.FigureCanvasBase.stop_event_loop_default.__doc__ + _JQUERY_ICON_CLASSES = { 'home': 'ui-icon ui-icon-home', 'back': 'ui-icon ui-icon-circle-arrow-w', @@ -247,10 +248,11 @@ def stop_event_loop(self): None: None, } + class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2): # Use the standard toolbar items + download button - toolitems = [(text, tooltip_text, _jquery_icon_classes[image_file], + toolitems = [(text, tooltip_text, _JQUERY_ICON_CLASSES[image_file], name_of_method) for text, tooltip_text, image_file, name_of_method in (backend_bases.NavigationToolbar2.toolitems + From 99c04e9b46024b7c346e26ea3bd14879182f0c3f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 6 Sep 2014 00:08:43 -0400 Subject: [PATCH 03/45] BLD/BUG/DOC : made pytz a required dependency closes #3423 --- INSTALL | 11 +++++------ setup.py | 1 + setupext.py | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/INSTALL b/INSTALL index dbd9716b3c22..1a854e5ae544 100644 --- a/INSTALL +++ b/INSTALL @@ -201,6 +201,11 @@ libpng 1.2 (or later) `__). libpng requires zlib. +`pytz` + Used to manipulate time-zone aware datetimes. + + + Optional GUI framework ^^^^^^^^^^^^^^^^^^^^^^ @@ -247,12 +252,6 @@ Optional dependencies freetype 2.3 available, please edit L945 of `setupext.py` to reduce `min_version` to 2.3. -`pytz` - Required if you want to manipulate datetime objects which are time-zone - aware. An exception will be raised if you try to make time-zone aware - plots with out `pytz` installed. It will become a required dependency - in 1.4.1. - Required libraries that ship with matplotlib ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/setup.py b/setup.py index 64e3d7e38c40..aeeb5816f38b 100644 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ setupext.Numpy(), setupext.Six(), setupext.Dateutil(), + setupext.Pytz(), setupext.Tornado(), setupext.Pyparsing(), setupext.CXX(), diff --git a/setupext.py b/setupext.py index 31f9b06d5c27..404c313498d1 100755 --- a/setupext.py +++ b/setupext.py @@ -984,6 +984,7 @@ def get_extension(self): self.add_flags(ext) return ext + class FT2Font(SetupPackage): name = 'ft2font' @@ -1183,6 +1184,22 @@ def get_install_requires(self): return ['six>={0}'.format(self.min_version)] +class Pytz(SetupPackage): + name = "pytz" + + def check(self): + try: + import pytz + except ImportError: + return ( + "pytz was not found.") + + return "using pytz version %s" % pytz.__version__ + + def get_install_requires(self): + return ['pytz'] + + class Dateutil(SetupPackage): name = "dateutil" From f40bf1482be0c098b14119a4eed86c215237fc49 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 6 Sep 2014 00:10:06 -0400 Subject: [PATCH 04/45] DOC : removed freetype version caveats minimum version for freetype is 2.3 closes #3412 --- INSTALL | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/INSTALL b/INSTALL index 1a854e5ae544..5c3e26f7b520 100644 --- a/INSTALL +++ b/INSTALL @@ -245,12 +245,8 @@ Optional dependencies selection of image file formats. -:term:`freetype` 2.4 or later - library for reading true type font files. Matplotlib in known - to work with freetype 2.3, and the required version will be reduced - in 1.4.1. If you need to build from source on a system which only has - freetype 2.3 available, please edit L945 of `setupext.py` to reduce - `min_version` to 2.3. +:term:`freetype` 2.3 or later + library for reading true type font files. Required libraries that ship with matplotlib From ac6260672d1144a73f29f00a9151f52ed35adfdc Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 6 Sep 2014 14:21:25 -0400 Subject: [PATCH 05/45] BLD : updated message when to pytz found --- setupext.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setupext.py b/setupext.py index 404c313498d1..5c5ed82067fb 100755 --- a/setupext.py +++ b/setupext.py @@ -1192,7 +1192,9 @@ def check(self): import pytz except ImportError: return ( - "pytz was not found.") + "pytz was not found. " + "pip will attempt to install it " + "after matplotlib.") return "using pytz version %s" % pytz.__version__ From 3a9757cf2c79d28298b5901ed01e00fe4448f997 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 6 Sep 2014 16:54:27 -0400 Subject: [PATCH 06/45] BUG : fix encoding of png data When formatting the png data to send over the wire need to decode the byte string to ascii. If this is not done the literal string sent to the browser is: "data:image/png;base64,b'iVBOR...'" instead of "data:image/png;base64,iVBOR..." The extra b' makes the string no longer a valid png which is why we were getting white boxes --- lib/matplotlib/backends/backend_nbagg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 7ca9257ff389..598256917da5 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -3,6 +3,7 @@ import json import io import os +import six from uuid import uuid4 as uuid from IPython.display import display, Javascript, HTML @@ -193,7 +194,10 @@ def send_json(self, content): def send_binary(self, blob): # The comm is ascii, so we always send the image in base64 # encoded data URL form. - data_uri = "data:image/png;base64,{0}".format(b64encode(blob)) + data = b64encode(blob) + if six.PY3: + data = data.decode('ascii') + data_uri = "data:image/png;base64,{0}".format(data) self.comm.send({'data': data_uri}) def on_message(self, message): From 0a41d838ac8f0c4e5aba43e0c52872312f71564c Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 12 Sep 2014 12:58:46 -0400 Subject: [PATCH 07/45] Allow running from non-ascii directory on Windows --- lib/matplotlib/__init__.py | 6 +++++- lib/matplotlib/sphinxext/plot_directive.py | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 25d7e04f3103..8a20272636f0 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -745,7 +745,11 @@ def matplotlib_fname(): - Lastly, it looks in `$MATPLOTLIBDATA/matplotlibrc` for a system-defined copy. """ - fname = os.path.join(os.getcwd(), 'matplotlibrc') + if six.PY2: + cwd = os.getcwdu() + else: + cwd = os.getcwd() + fname = os.path.join(cwd, 'matplotlibrc') if os.path.exists(fname): return fname diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 08093fe2a7ec..efc41f284dc8 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -447,8 +447,10 @@ def run_code(code, code_path, ns=None, function_name=None): # Change the working directory to the directory of the example, so # it can get at its data files, if any. Add its path to sys.path # so it can import any helper modules sitting beside it. - - pwd = os.getcwd() + if six.PY2: + pwd = os.getcwdu() + else: + pwd = os.getcwd() old_sys_path = list(sys.path) if setup.config.plot_working_directory is not None: try: From 81760d7bd3491a1c2c02ad840c9093d76a15f129 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 16 Sep 2014 09:41:43 +0100 Subject: [PATCH 08/45] Remove backticks around None. This is not a ref --- lib/matplotlib/colorbar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index b16f01c50c34..6862105bc36e 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1014,12 +1014,12 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, Keyword arguments may include the following (with defaults): - location : [`None`|'left'|'right'|'top'|'bottom'] + location : [None|'left'|'right'|'top'|'bottom'] The position, relative to **parents**, where the colorbar axes should be created. If None, the value will either come from the given ``orientation``, else it will default to 'right'. - orientation : [`None`|'vertical'|'horizontal'] + orientation : [None|'vertical'|'horizontal'] The orientation of the colorbar. Typically, this keyword shouldn't be used, as it can be derived from the ``location`` keyword. From 630f867dae6a4af89cf79150cbd0c27e999f66e7 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Tue, 16 Sep 2014 09:42:22 +0100 Subject: [PATCH 09/45] Fix formatting in docstring for dates --- lib/matplotlib/dates.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 227b39945b31..5f2be9edbc32 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -520,22 +520,23 @@ class AutoDateFormatter(ticker.Formatter): >>> formatter = AutoDateFormatter() >>> formatter.scaled[1/(24.*60.)] = '%M:%S' # only show min and sec - Custom `FunctionFormatter`s can also be used. The following example shows - how to use a custom format function to strip trailing zeros from decimal - seconds and adds the date to the first ticklabel:: - - >>> def my_format_function(x, pos=None): - ... x = matplotlib.dates.num2date(x) - ... if pos == 0: - ... fmt = '%D %H:%M:%S.%f' - ... else: - ... fmt = '%H:%M:%S.%f' - ... label = x.strftime(fmt) - ... label = label.rstrip("0") - ... label = label.rstrip(".") - ... return label - >>> from matplotlib.ticker import FuncFormatter - >>> formatter.scaled[1/(24.*60.)] = FuncFormatter(my_format_function) + A custom :class:`~matplotlib.ticker.FuncFormatter` can also be used. + The following example shows how to use a custom format function to strip + trailing zeros from decimal seconds and adds the date to the first + ticklabel:: + + >>> def my_format_function(x, pos=None): + ... x = matplotlib.dates.num2date(x) + ... if pos == 0: + ... fmt = '%D %H:%M:%S.%f' + ... else: + ... fmt = '%H:%M:%S.%f' + ... label = x.strftime(fmt) + ... label = label.rstrip("0") + ... label = label.rstrip(".") + ... return label + >>> from matplotlib.ticker import FuncFormatter + >>> formatter.scaled[1/(24.*60.)] = FuncFormatter(my_format_function) """ # This can be improved by providing some user-level direction on From ef72f6da1e22e42051bc31bfed189b9ee552d803 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 16 Sep 2014 11:46:12 -0400 Subject: [PATCH 10/45] Clarify matplotlib/PyCXX incompatibility --- setupext.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setupext.py b/setupext.py index 63e87d1352b0..77c4e33fde6b 100755 --- a/setupext.py +++ b/setupext.py @@ -830,10 +830,13 @@ class CXX(SetupPackage): def check(self): if PY3: # There is no version of PyCXX in the wild that will work - # with Python 3.x + # with Python 3.x and matplotlib, since they lack support + # for the buffer object. self.__class__.found_external = False - return ("Official versions of PyCXX are not compatible with " - "Python 3.x. Using local copy") + return ("Official versions of PyCXX are not compatible " + "with matplotlib on Python 3.x, since they lack " + "support for the buffer object. Using local " + "copy") self.__class__.found_external = True old_stdout = sys.stdout From a6dc25df3a6879a380c40bd74eb0227dcebd0a84 Mon Sep 17 00:00:00 2001 From: Cimarron Mittelsteadt Date: Tue, 16 Sep 2014 11:08:32 -0700 Subject: [PATCH 11/45] BUG: Take the absolute difference between times when determining the correct tick interval --- lib/matplotlib/dates.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 227b39945b31..417543604327 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -898,6 +898,10 @@ def get_locator(self, dmin, dmax): 'Pick the best locator based on a distance.' delta = relativedelta(dmax, dmin) + # take absolute difference + if dmin > dmax: + delta = -delta + numYears = (delta.years * 1.0) numMonths = (numYears * 12.0) + delta.months numDays = (numMonths * 31.0) + delta.days From 342450636fb83a9b05ddbce1be1128570ff90536 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 16 Sep 2014 18:19:02 -0400 Subject: [PATCH 12/45] BUG : fix eps corruption when using clipping _get_clip_path was returning '' instead of the ps-function name like it should have been. This was introduced in 9b9c0c6fbdd53ec42e2a1edecc9c6895b6f7a3ef in PR #2927. Closes #3523 --- lib/matplotlib/backends/backend_ps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index d2e8bc4c7e1e..0acd8896f42f 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -568,7 +568,7 @@ def _get_clip_path(self, clippath, clippath_transform): ps_cmd.extend(['clip', 'newpath', '} bind def\n']) self._pswriter.write('\n'.join(ps_cmd)) self._clip_paths[key] = pid - return id + return pid def draw_path(self, gc, path, transform, rgbFace=None): """ From 301fa0a8fb873f5509fe20612336c50167875d07 Mon Sep 17 00:00:00 2001 From: Jens H Nielsen Date: Wed, 17 Sep 2014 22:03:34 +0100 Subject: [PATCH 13/45] Only insert links to pdfs if we are actually generating these. This should silence most warnings in the html only small build on travis --- lib/matplotlib/sphinxext/plot_directive.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 08093fe2a7ec..8080f3de80c5 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -383,7 +383,9 @@ def remove_coding(text): {{ only_latex }} {% for img in images %} + {% if 'pdf' in img.formats -%} .. image:: {{ build_dir }}/{{ img.basename }}.pdf + {% endif -%} {% endfor %} {{ only_texinfo }} From d10e26ef17f769b9374d9a5beab59b268727e565 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 17 Sep 2014 22:31:24 -0400 Subject: [PATCH 14/45] Merge pull request #3510 from blink1073/fix-setupext BLD : improve setupext.py on windows --- setupext.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/setupext.py b/setupext.py index 77c4e33fde6b..92fabd255e5b 100755 --- a/setupext.py +++ b/setupext.py @@ -120,6 +120,8 @@ def has_include_file(include_dirs, filename): Returns `True` if `filename` can be found in one of the directories in `include_dirs`. """ + if sys.platform == 'win32': + include_dirs += os.environ.get('INCLUDE', '.').split(';') for dir in include_dirs: if os.path.exists(os.path.join(dir, filename)): return True @@ -130,8 +132,6 @@ def check_include_file(include_dirs, filename, package): """ Raises an exception if the given include file can not be found. """ - if sys.platform == 'win32': - include_dirs.extend(os.getenv('INCLUDE', '.').split(';')) if not has_include_file(include_dirs, filename): raise CheckFailed( "The C/C++ header for %s (%s) could not be found. You " @@ -156,6 +156,13 @@ def get_base_dirs(): return basedir_map.get(sys.platform, ['/usr/local', '/usr']) +def get_include_dirs(): + """ + Returns a list of standard include directories on this platform. + """ + return [os.path.join(d, 'include') for d in get_base_dirs()] + + def is_min_version(found, minversion): """ Returns `True` if `found` is at least as high a version as @@ -930,7 +937,8 @@ class FreeType(SetupPackage): def check(self): if sys.platform == 'win32': - return "Unknown version" + check_include_file(get_include_dirs(), 'ft2build.h', 'freetype') + return 'Using unknown version found on system.' status, output = getstatusoutput("freetype-config --ftversion") if status == 0: @@ -1007,7 +1015,8 @@ class Png(SetupPackage): def check(self): if sys.platform == 'win32': - return "Unknown version" + check_include_file(get_include_dirs(), 'png.h', 'png') + return 'Using unknown version found on system.' status, output = getstatusoutput("libpng-config --version") if status == 0: @@ -1020,9 +1029,7 @@ def check(self): 'libpng', 'png.h', min_version='1.2', version=version) except CheckFailed as e: - include_dirs = [ - os.path.join(dir, 'include') for dir in get_base_dirs()] - if has_include_file(include_dirs, 'png.h'): + if has_include_file(get_include_dirs(), 'png.h'): return str(e) + ' Using unknown version found on system.' raise @@ -1053,7 +1060,7 @@ def check(self): # present on this system, so check if the header files can be # found. include_dirs = [ - os.path.join(x, 'include', 'qhull') for x in get_base_dirs()] + os.path.join(x, 'qhull') for x in get_include_dirs()] if has_include_file(include_dirs, 'qhull_a.h'): return 'Using system Qhull (version unknown, no pkg-config info)' else: From 4234f0fec6a0ac9d2075b429bb18870f5d1654c8 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 17 Sep 2014 23:52:22 -0400 Subject: [PATCH 15/45] BUG : fix handling of flierprop by boxplot - moved logic to parse sym from bxp -> boxplot - restored documented behavior with `sym=''` - change default value of sym from 'b+' -> None - should not be visible from outside as default behavior remains the same closes #3459 --- lib/matplotlib/axes/_axes.py | 58 +++++++++++++++++++++++------------- lib/matplotlib/pyplot.py | 2 +- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 7301c2c93157..f3fadc8cc018 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2883,7 +2883,7 @@ def xywhere(xs, ys, mask): return errorbar_container # (l0, caplines, barcols) - def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, + def boxplot(self, x, notch=False, sym=None, vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, bootstrap=None, usermedians=None, conf_intervals=None, meanline=False, showmeans=False, showcaps=True, @@ -2919,9 +2919,11 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, If False, produces a rectangular box plot. If True, will produce a notched box plot - sym : str, default = 'b+' + sym : str or None, default = None The default symbol for flier points. Enter an empty string ('') if you don't want to show fliers. + If `None`, then the fliers default to 'b+' If you want more + control use the fliersprop kwarg. vert : bool, default = False If True (default), makes the boxes vertical. @@ -3043,10 +3045,39 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, """ bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap, labels=labels) + # make sure we have a dictionary if flierprops is None: - flierprops = dict(sym=sym) - else: - flierprops['sym'] = sym + flierprops = dict() + # if non-default sym value, put it into the flier dictionary + # the logic for providing the default symbol ('b+') now lives + # in bxp in the initial value of final_flierprops + # handle all of the `sym` related logic here so we only have to pass + # on the flierprops dict. + if sym is not None: + # no-flier case, which should really be done with + # 'showfliers=False' but none-the-less deal with it to keep back + # compatibility + if sym == '': + # blow away existing dict and make one for invisible markers + flierprops = dict(linestyle='none', marker='', + markeredgecolor='none', + markerfacecolor='none') + # now process the symbol string + else: + # process the symbol string + # discarded linestyle + _, marker, color = _process_plot_format(sym) + # if we have a marker, use it + if marker is not None: + flierprops['marker'] = marker + # if we have a color, use it + if color is not None: + flierprops['color'] = color + # assume that if color is passed in the user want + # filled symbol, if the users want more control use + # flierprops + flierprops['markeredgecolor'] = color + flierprops['markerfacecolor'] = color # replace medians if necessary: if usermedians is not None: @@ -3288,24 +3319,9 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, final_flierprops = dict(linestyle='none', marker='+', markeredgecolor='b', markerfacecolor='none') + # flier (outlier) properties if flierprops is not None: - sym = flierprops.pop('sym', None) - - # watch inverted logic, checks for non-default - # value of `sym` - if not (sym == '' or (sym is None)): - # process the symbol string - # discarded linestyle - _, marker, color = _process_plot_format(sym) - if marker is not None: - flierprops['marker'] = marker - if color is not None: - flierprops['color'] = color - # assume that if color is passed in the user want - # filled symbol - flierprops['markeredgecolor'] = color - flierprops['markerfacecolor'] = color final_flierprops.update(flierprops) # median line properties diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 5bd6488824e9..062d8dd7769e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2611,7 +2611,7 @@ def broken_barh(xranges, yrange, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.boxplot) -def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, +def boxplot(x, notch=False, sym=None, vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, bootstrap=None, usermedians=None, conf_intervals=None, meanline=False, showmeans=False, showcaps=True, showbox=True, showfliers=True, boxprops=None, labels=None, From 8158393186c71d8529cab487d1542bdfdc01f128 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 18 Sep 2014 00:08:11 -0400 Subject: [PATCH 16/45] BUG/DOC : Correct default value listed in docstring Changed to be True to match the code closes #3455 --- 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 7301c2c93157..32b565218ad0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2923,7 +2923,7 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, The default symbol for flier points. Enter an empty string ('') if you don't want to show fliers. - vert : bool, default = False + vert : bool, default = True If True (default), makes the boxes vertical. If False, makes horizontal boxes. From 8496e060adf904e4543cde327db8a1d7cdcd97bd Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 18 Sep 2014 08:23:32 -0400 Subject: [PATCH 17/45] Merge pull request #3369 from e-q/legendFrameAlpha BUG : Added legend.framealpha to rcParams, as mentioned in axes.legend docstring --- lib/matplotlib/legend.py | 4 +++- lib/matplotlib/rcsetup.py | 3 ++- matplotlibrc.template | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 928f52c3b8a6..23949ce0e562 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -367,7 +367,9 @@ def __init__(self, parent, handles, labels, # init with null renderer self._init_legend_box(handles, labels) - if framealpha is not None: + if framealpha is None: + self.get_frame().set_alpha(rcParams["legend.framealpha"]) + else: self.get_frame().set_alpha(framealpha) self._loc = loc diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 7d4d97df1572..f535aec084e6 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -632,7 +632,8 @@ def __call__(self, s): 'legend.shadow': [False, validate_bool], # whether or not to draw a frame around legend 'legend.frameon': [True, validate_bool], - + # alpha value of the legend frame + 'legend.framealpha': [1.0, validate_float], ## the following dimensions are in fraction of the font size 'legend.borderpad': [0.4, validate_float], # units are fontsize diff --git a/matplotlibrc.template b/matplotlibrc.template index 6edf352f7ae5..bec6ea7e4312 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -316,6 +316,7 @@ backend : %(backend)s #legend.columnspacing : 2. # the border between the axes and legend edge in fraction of fontsize #legend.shadow : False #legend.frameon : True # whether or not to draw a frame around legend +#legend.framealpha : 1.0 # opacity of of legend frame #legend.scatterpoints : 3 # number of scatter points ### FIGURE From 46f0d4bdec919ec48351e49fb0939b6baa07aa5a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 18 Sep 2014 00:03:49 -0400 Subject: [PATCH 18/45] BUG : fixes whis over-writing in boxplot_stats Restore the value of `whis` to the input value at the top of each pass through the for-loop. @weathergod hit the nail on the head. closes #3468 --- lib/matplotlib/cbook.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index df217258ec14..f990e597682f 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1954,11 +1954,15 @@ def _compute_conf_interval(data, med, iqr, bootstrap): elif len(labels) != ncols: raise ValueError("Dimensions of labels and X must be compatible") + input_whis = whis for ii, (x, label) in enumerate(zip(X, labels), start=0): # empty dict stats = {} stats['label'] = label + # restore whis to the input values in case it got changed in the loop + whis = input_whis + # arithmetic mean stats['mean'] = np.mean(x) From 350413cb1abc8bb07d52b95e92c039f2d50ee0a6 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 18 Sep 2014 10:14:42 -0400 Subject: [PATCH 19/45] Fix marker clipping rectangle. --- src/_backend_agg.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index b088c97c9aa3..5aab419f5125 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -769,10 +769,10 @@ RendererAgg::draw_markers(const Py::Tuple& args) agg::serialized_scanlines_adaptor_aa8::embedded_scanline sl; agg::rect_d clipping_rect( - -(scanlines.min_x() + 1.0), - (scanlines.max_y() + 1.0), - width + scanlines.max_x() + 1.0, - height - scanlines.min_y() + 1.0); + -1.0 - scanlines.max_x(), + -1.0 - scanlines.max_y(), + 1.0 + width - scanlines.min_x(), + 1.0 + height - scanlines.min_y()); if (has_clippath) { From 014c11277c545a6069e40b814724b5ade46f7b54 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 18 Sep 2014 16:51:45 -0400 Subject: [PATCH 20/45] DEP : update six minimum version turns out we really need version 1.4 or better fixes #3538 --- INSTALL | 2 +- setupext.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/INSTALL b/INSTALL index f96f230a79cc..d44ec5048b15 100644 --- a/INSTALL +++ b/INSTALL @@ -192,7 +192,7 @@ Required Dependencies using pip, easy_install or installing from source, the installer will attempt to download and install `pyparsing` from PyPI. -six 1.3 or later +six 1.4 or later Python 2/3 compatibility library. This is also a dependency of :term:`dateutil`. diff --git a/setupext.py b/setupext.py index 92fabd255e5b..43ba888d9b0c 100755 --- a/setupext.py +++ b/setupext.py @@ -1179,7 +1179,7 @@ def get_extension(self): class Six(SetupPackage): name = "six" - min_version = "1.3" + min_version = "1.4" def check(self): try: From 34230a724c29acab35e7e0b905016023c20c4833 Mon Sep 17 00:00:00 2001 From: Cimarron Mittelsteadt Date: Thu, 18 Sep 2014 19:42:38 -0700 Subject: [PATCH 21/45] TST: Added test for inverted limit with autodatelocator --- .../test_dates/date_inverted_limit.png | Bin 0 -> 26755 bytes lib/matplotlib/tests/test_dates.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 lib/matplotlib/tests/baseline_images/test_dates/date_inverted_limit.png diff --git a/lib/matplotlib/tests/baseline_images/test_dates/date_inverted_limit.png b/lib/matplotlib/tests/baseline_images/test_dates/date_inverted_limit.png new file mode 100644 index 0000000000000000000000000000000000000000..73e635eb5189f4f17e47829e5a9a66d3efd00214 GIT binary patch literal 26755 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 Date: Thu, 18 Sep 2014 19:55:10 -0700 Subject: [PATCH 22/45] STY/TST: pep8 fixes for test_dates --- lib/matplotlib/tests/test_coding_standards.py | 1 - lib/matplotlib/tests/test_dates.py | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/tests/test_coding_standards.py b/lib/matplotlib/tests/test_coding_standards.py index 3c63cd0c4a24..24bda42fba23 100644 --- a/lib/matplotlib/tests/test_coding_standards.py +++ b/lib/matplotlib/tests/test_coding_standards.py @@ -96,7 +96,6 @@ '*/matplotlib/tri/triinterpolate.py', '*/matplotlib/tests/test_axes.py', '*/matplotlib/tests/test_bbox_tight.py', - '*/matplotlib/tests/test_dates.py', '*/matplotlib/tests/test_delaunay.py', '*/matplotlib/tests/test_dviread.py', '*/matplotlib/tests/test_image.py', diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 49368c0dd1e5..7b847bb0df6d 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -162,9 +162,9 @@ def test_DateFormatter(): def test_date_formatter_callable(): scale = -11 locator = mock.Mock(_get_unit=mock.Mock(return_value=scale)) - callable_formatting_function = lambda dates, _: \ - [dt.strftime('%d-%m//%Y') for dt in dates] - + callable_formatting_function = (lambda dates, _: + [dt.strftime('%d-%m//%Y') for dt in dates]) + formatter = mdates.AutoDateFormatter(locator) formatter.scaled[-10] = callable_formatting_function assert_equal(formatter([datetime.datetime(2014, 12, 25)]), @@ -223,7 +223,8 @@ def test_auto_date_locator(): def _create_auto_date_locator(date1, date2): locator = mdates.AutoDateLocator() locator.create_dummy_axis() - locator.set_view_interval(mdates.date2num(date1), mdates.date2num(date2)) + locator.set_view_interval(mdates.date2num(date1), + mdates.date2num(date2)) return locator d1 = datetime.datetime(1990, 1, 1) @@ -275,8 +276,10 @@ def _create_auto_date_locator(date1, date2): '1990-01-01 00:00:40+00:00'] ], [datetime.timedelta(microseconds=1500), - ['1989-12-31 23:59:59.999507+00:00', '1990-01-01 00:00:00+00:00', - '1990-01-01 00:00:00.000502+00:00', '1990-01-01 00:00:00.001005+00:00', + ['1989-12-31 23:59:59.999507+00:00', + '1990-01-01 00:00:00+00:00', + '1990-01-01 00:00:00.000502+00:00', + '1990-01-01 00:00:00.001005+00:00', '1990-01-01 00:00:00.001508+00:00'] ], ) From 31f305ed4e146ee01d9fe4cc0b427670745c0cfb Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Fri, 19 Sep 2014 17:33:35 +0100 Subject: [PATCH 23/45] Extended the nbagg backend to support animation. --- lib/matplotlib/backends/backend_nbagg.py | 83 +++- lib/matplotlib/backends/backend_webagg.py | 29 +- .../backends/web_backend/nbagg_mpl.js | 5 +- .../backends/web_backend/nbagg_uat.ipynb | 365 ++++++++++++++++++ 4 files changed, 437 insertions(+), 45 deletions(-) create mode 100644 lib/matplotlib/backends/web_backend/nbagg_uat.ipynb diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index c69c558ea071..907c737419b5 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -5,6 +5,8 @@ import os from uuid import uuid4 as uuid +import tornado.ioloop + from IPython.display import display, Javascript, HTML from IPython.kernel.comm import Comm @@ -12,24 +14,23 @@ from matplotlib.backends.backend_webagg_core import (FigureManagerWebAgg, FigureCanvasWebAggCore, NavigationToolbar2WebAgg) -from matplotlib.backend_bases import ShowBase, NavigationToolbar2 +from matplotlib.backend_bases import ShowBase, NavigationToolbar2, TimerBase class Show(ShowBase): def __call__(self, block=None): - import matplotlib._pylab_helpers as pylab_helpers + from matplotlib._pylab_helpers import Gcf from matplotlib import is_interactive - managers = pylab_helpers.Gcf.get_all_fig_managers() + managers = Gcf.get_all_fig_managers() if not managers: return - interactive = is_interactive() - for manager in managers: manager.show() - if not interactive and manager in pylab_helpers.Gcf._activeQue: - pylab_helpers.Gcf._activeQue.remove(manager) + + if not is_interactive() and manager in Gcf._activeQue: + Gcf._activeQue.remove(manager) show = Show() @@ -48,19 +49,18 @@ def draw_if_interactive(): def connection_info(): """ Return a string showing the figure and connection status for - the backend. + the backend. This is intended as a diagnostic tool, and not for general + use. """ - # TODO: Make this useful! - import matplotlib._pylab_helpers as pylab_helpers + from matplotlib._pylab_helpers import Gcf result = [] - for manager in pylab_helpers.Gcf.get_all_fig_managers(): + for manager in Gcf.get_all_fig_managers(): fig = manager.canvas.figure result.append('{} - {}'.format((fig.get_label() or "Figure {0}".format(manager.num)), manager.web_sockets)) - result.append('Figures pending show: ' + - str(len(pylab_helpers.Gcf._activeQue))) + result.append('Figures pending show: {}'.format(len(Gcf._activeQue))) return '\n'.join(result) @@ -93,7 +93,8 @@ def __init__(self, canvas, num): def display_js(self): # XXX How to do this just once? It has to deal with multiple - # browser instances using the same kernel. + # browser instances using the same kernel (require.js - but the + # file isn't static?). display(Javascript(FigureManagerNbAgg.get_javascript())) def show(self): @@ -105,6 +106,10 @@ def show(self): self._shown = True def reshow(self): + """ + A special method to re-show the figure in the notebook. + + """ self._shown = False self.show() @@ -137,6 +142,43 @@ def destroy(self): for comm in self.web_sockets.copy(): comm.on_close() + def clearup_closed(self): + """Clear up any closed Comms.""" + self.web_sockets = set([socket for socket in self.web_sockets + if not socket.is_open()]) + + +class TimerTornado(TimerBase): + def _timer_start(self): + import datetime + self._timer_stop() + if self._single: + ioloop = tornado.ioloop.IOLoop.instance() + self._timer = ioloop.add_timeout( + datetime.timedelta(milliseconds=self.interval), + self._on_timer) + else: + self._timer = tornado.ioloop.PeriodicCallback( + self._on_timer, + self.interval) + self._timer.start() + + def _timer_stop(self): + if self._timer is not None: + self._timer.stop() + self._timer = None + + def _timer_set_interval(self): + # Only stop and restart it if the timer has already been started + if self._timer is not None: + self._timer_stop() + self._timer_start() + + +class FigureCanvasNbAgg(FigureCanvasWebAggCore): + def new_timer(self, *args, **kwargs): + return TimerTornado(*args, **kwargs) + def new_figure_manager(num, *args, **kwargs): """ @@ -151,7 +193,7 @@ def new_figure_manager_given_figure(num, figure): """ Create a new figure manager instance for the given figure. """ - canvas = FigureCanvasWebAggCore(figure) + canvas = FigureCanvasNbAgg(figure) manager = FigureManagerNbAgg(canvas, num) return manager @@ -170,6 +212,8 @@ def __init__(self, manager): self.supports_binary = None self.manager = manager self.uuid = str(uuid()) + # Publish an output area with a unique ID. The javascript can then + # hook into this area. display(HTML("
" % self.uuid)) try: self.comm = Comm('matplotlib', data={'id': self.uuid}) @@ -178,12 +222,17 @@ def __init__(self, manager): 'instance. Are you in the IPython notebook?') self.comm.on_msg(self.on_message) + manager = self.manager + self.comm.on_close(lambda close_message: manager.clearup_closed()) + + def is_open(self): + return not self.comm._closed + def on_close(self): # When the socket is closed, deregister the websocket with # the FigureManager. - if self.comm in self.manager.web_sockets: - self.manager.remove_web_socket(self) self.comm.close() + self.manager.clearup_closed() def send_json(self, content): self.comm.send({'data': json.dumps(content)}) diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index 05e0e1cb23c6..e2330780ca12 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -39,6 +39,7 @@ from matplotlib.figure import Figure from matplotlib._pylab_helpers import Gcf from . import backend_webagg_core as core +from . import backend_nbagg def new_figure_manager(num, *args, **kwargs): @@ -96,39 +97,13 @@ def run(self): webagg_server_thread = ServerThread() -class TimerTornado(backend_bases.TimerBase): - def _timer_start(self): - self._timer_stop() - if self._single: - ioloop = tornado.ioloop.IOLoop.instance() - self._timer = ioloop.add_timeout( - datetime.timedelta(milliseconds=self.interval), - self._on_timer) - else: - self._timer = tornado.ioloop.PeriodicCallback( - self._on_timer, - self.interval) - self._timer.start() - - def _timer_stop(self): - if self._timer is not None: - self._timer.stop() - self._timer = None - - def _timer_set_interval(self): - # Only stop and restart it if the timer has already been started - if self._timer is not None: - self._timer_stop() - self._timer_start() - - class FigureCanvasWebAgg(core.FigureCanvasWebAggCore): def show(self): # show the figure window show() def new_timer(self, *args, **kwargs): - return TimerTornado(*args, **kwargs) + return backend_nbagg.TimerTornado(*args, **kwargs) def start_event_loop(self, timeout): backend_bases.FigureCanvasBase.start_event_loop_default( diff --git a/lib/matplotlib/backends/web_backend/nbagg_mpl.js b/lib/matplotlib/backends/web_backend/nbagg_mpl.js index caac0c17656d..3232407fa8b0 100644 --- a/lib/matplotlib/backends/web_backend/nbagg_mpl.js +++ b/lib/matplotlib/backends/web_backend/nbagg_mpl.js @@ -25,6 +25,8 @@ mpl.mpl_figure_comm = function(comm, msg) { // starts-up an IPython Comm through the "matplotlib" channel. var id = msg.content.data.id; + // Get hold of the div created by the display call when the Comm + // socket was opened in Python. var element = $("#" + id); var ws_proxy = comm_websocket_adapter(comm) @@ -44,7 +46,7 @@ mpl.mpl_figure_comm = function(comm, msg) { // Disable right mouse context menu. $(fig.rubberband_canvas).bind("contextmenu",function(e){ - return false; + return false; }); }; @@ -59,6 +61,7 @@ mpl.figure.prototype.handle_close = function(fig, msg) { } mpl.figure.prototype.push_to_output = function(remove_interactive) { + // Turn the data on the canvas into data in the output cell. var dataURL = this.canvas.toDataURL(); this.cell_info[1]['text/html'] = ''; } diff --git a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb new file mode 100644 index 000000000000..81e47b041e34 --- /dev/null +++ b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb @@ -0,0 +1,365 @@ +{ + "metadata": { + "name": "", + "signature": "sha256:9a73a15660e6912c6bcfaf7c9e8247503738ce5d590a34e08789c2e16de93080" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## UAT for NbAgg backend.\n", + "\n", + "The first line simply reloads matplotlib, uses the nbagg backend and then reloads the backend, just to ensure we have the latest modification to the backend code. Note: The underlying JavaScript will not be updated by this process, so a refresh of the browser after clearing the output and saving is necessary to clear everything fully." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import matplotlib\n", + "reload(matplotlib)\n", + "\n", + "\n", + "matplotlib.use('nbagg')\n", + "\n", + "import matplotlib.backends.backend_nbagg\n", + "reload(matplotlib.backends.backend_nbagg)" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 1 - Simple figure creation using pyplot\n", + "\n", + "Should produce a figure window which is interactive with the pan and zoom buttons. (Do not press the close button, but any others may be used)." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import matplotlib.pyplot as plt\n", + "plt.interactive(False)\n", + "\n", + "fig1 = plt.figure()\n", + "plt.plot(range(10))\n", + "\n", + "plt.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 2 - Creation of another figure, without the need to do plt.figure.\n", + "\n", + "As above, a new figure should be created." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt.plot([3, 2, 1])\n", + "plt.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 3 - Connection info\n", + "\n", + "The printout should show that there are two figures which have active CommSockets, and no figures pending show." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "print matplotlib.backends.backend_nbagg.connection_info()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 4 - Closing figures\n", + "\n", + "Closing a specific figure instance should turn the figure into a plain image - the UI should have been removed. In this case, scroll back to the first figure and assert this is the case." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt.close(fig1)" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 5 - No show without plt.show in non-interactive mode\n", + "\n", + "Simply doing a plt.plot should not show a new figure, nor indeed update an existing one (easily verified in UAT 6).\n", + "The output should simply be a list of Line2D instances." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt.plot(range(10))" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 6 - Connection information\n", + "\n", + "We just created a new figure, but didn't show it. Connection info should no longer have \"Figure 1\" (as we closed it in UAT 4) and should have figure 2 and 3, with Figure 3 without any connections. There should be 1 figure pending." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "print matplotlib.backends.backend_nbagg.connection_info()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 7 - Show of previously created figure\n", + "\n", + "We should be able to show a figure we've previously created. The following should produce two figure windows." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt.show()\n", + "plt.figure()\n", + "plt.plot(range(5))\n", + "plt.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 8 - Interactive mode\n", + "\n", + "In interactive mode, creating a line should result in a figure being shown." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt.interactive(True)\n", + "plt.figure()\n", + "plt.plot([3, 2, 1])" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Subsequent lines should be added to the existing figure, rather than creating a new one." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt.plot(range(3))" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Disable interactive mode again." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt.interactive(False)" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 9 - Multiple shows\n", + "\n", + "Unlike most of the other matplotlib backends, we may want to see a figure multiple times (with or without synchronisation between the views, though the former is not yet implemented). Assert that plt.gcf().canvas.manager.reshow() results in another figure window which is synchronised upon pan & zoom." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt.gcf().canvas.manager.reshow()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 10 - Saving notebook\n", + "\n", + "Saving the notebook (with CTRL+S or File->Save) should result in the saved notebook having static versions of the figues embedded within. The image should be the last update from user interaction and interactive plotting. (check by converting with ``ipython nbconvert ``)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 11 - Creation of a new figure on second show\n", + "\n", + "Create a figure, show it, then create a new axes and show it. The result should be a new figure.\n", + "\n", + "**BUG: Sometimes this doesn't work - not sure why (@pelson).**" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "fig = plt.figure()\n", + "plt.axes()\n", + "plt.show()\n", + "\n", + "plt.plot([1, 2, 3])\n", + "plt.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT 12 - OO interface\n", + "\n", + "Should produce a new figure and plot it." + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from matplotlib.backends.backend_nbagg import new_figure_manager,show\n", + "\n", + "manager = new_figure_manager(1000)\n", + "fig = manager.canvas.figure\n", + "ax = fig.add_subplot(1,1,1)\n", + "ax.plot([1,2,3])\n", + "fig.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## UAT 13 - Animation\n", + "\n", + "The following should generate an animated line:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import matplotlib.animation as animation\n", + "import numpy as np\n", + "\n", + "fig, ax = plt.subplots()\n", + "\n", + "x = np.arange(0, 2*np.pi, 0.01) # x-array\n", + "line, = ax.plot(x, np.sin(x))\n", + "\n", + "def animate(i):\n", + " line.set_ydata(np.sin(x+i/10.0)) # update the data\n", + " return line,\n", + "\n", + "#Init only required for blitting to give a clean slate.\n", + "def init():\n", + " line.set_ydata(np.ma.array(x, mask=True))\n", + " return line,\n", + "\n", + "ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,\n", + " interval=32., blit=True)\n", + "plt.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### UAT ?? - Keyboard shortcuts in IPython after close of figure (Current known bug)\n", + "### UAT ?? - Race condition to show means that \"run all\" ends up collapsing some figures into one (Current known bug)" + ] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file From 11748bfba7440ddeb3f75337cfb1f46c623da25f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 19 Sep 2014 16:51:16 -0400 Subject: [PATCH 24/45] DOC : fix main-page tags
 ->  for inline markup.
---
 doc/_templates/index.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/doc/_templates/index.html b/doc/_templates/index.html
index 664e92fcf14e..0d6f5754134b 100644
--- a/doc/_templates/index.html
+++ b/doc/_templates/index.html
@@ -63,9 +63,9 @@ 

Introduction

For a sampling, see the
screenshots, thumbnail gallery, and examples directory

-

For simple plotting the

pyplot
interface provides a +

For simple plotting the pyplot interface provides a MATLAB-like interface, particularly when combined - with

IPython
. For the power user, you have full control + with IPython. For the power user, you have full control of line styles, font properties, axes properties, etc, via an object oriented interface or via a set of functions familiar to MATLAB users.

From b4fb45578ef022ac60233e8c6a1e72803150543f Mon Sep 17 00:00:00 2001 From: Jens H Nielsen Date: Thu, 18 Sep 2014 22:42:02 +0100 Subject: [PATCH 25/45] Fix warning in lines.py --- lib/matplotlib/lines.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 6343684a207d..cb7cd3ecb7d0 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -937,6 +937,7 @@ def set_linestyle(self, linestyle): ACCEPTS: [``'-'`` | ``'--'`` | ``'-.'`` | ``':'`` | ``'None'`` | ``' '`` | ``''``] + and any drawstyle in combination with a linestyle, e.g., ``'steps--'``. """ From fd558d93c42b37009807a4a5c399c285361218df Mon Sep 17 00:00:00 2001 From: Jens H Nielsen Date: Fri, 19 Sep 2014 19:58:29 +0100 Subject: [PATCH 26/45] Indent correctly in mlab --- lib/matplotlib/mlab.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/mlab.py b/lib/matplotlib/mlab.py index dea049076a5b..17f85dd997c3 100644 --- a/lib/matplotlib/mlab.py +++ b/lib/matplotlib/mlab.py @@ -394,6 +394,7 @@ def demean(x, axis=0): .. seealso:: :func:`delinear` + :func:`denone` :func:`delinear` and :func:`denone` are other detrend algorithms. @@ -427,6 +428,7 @@ def detrend_mean(x, axis=None): for the default *axis*. :func:`detrend_linear` + :func:`detrend_none` :func:`detrend_linear` and :func:`detrend_none` are other detrend algorithms. @@ -474,6 +476,7 @@ def detrend_none(x, axis=None): for the default *axis*, which has no effect. :func:`detrend_mean` + :func:`detrend_linear` :func:`detrend_mean` and :func:`detrend_linear` are other detrend algorithms. @@ -506,6 +509,7 @@ def detrend_linear(y): for the default *axis*. :func:`detrend_mean` + :func:`detrend_none` :func:`detrend_mean` and :func:`detrend_none` are other detrend algorithms. @@ -878,6 +882,7 @@ def _single_spectrum_helper(x, mode, Fs=None, window=None, pad_to=None, *detrend*: [ 'default' | 'constant' | 'mean' | 'linear' | 'none'] or callable + The function applied to each segment before fft-ing, designed to remove the mean or linear trend. Unlike in MATLAB, where the *detrend* parameter is a vector, in @@ -1244,6 +1249,7 @@ def specgram(x, NFFT=None, Fs=None, detrend=None, window=None, *mode*: [ 'default' | 'psd' | 'complex' | 'magnitude' 'angle' | 'phase' ] + What sort of spectrum to use. Default is 'psd'. which takes the power spectral density. 'complex' returns the complex-valued frequency spectrum. 'magnitude' returns the magnitude spectrum. From b9ac3fc0f6f3f1e68e4d191c1724fc26f33488ec Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Fri, 19 Sep 2014 21:51:05 +0100 Subject: [PATCH 27/45] silence warnings in art3d --- lib/mpl_toolkits/mplot3d/art3d.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index ee985cc6de02..6381a685239f 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -436,9 +436,12 @@ def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True): :class:`Path3DCollection` object). Keywords: + *za* The location or locations to place the patches in the collection along the *zdir* axis. Defaults to 0. + *zdir* The axis in which to place the patches. Default is "z". + *depthshade* Whether to shade the patches to give a sense of depth. Defaults to *True*. From 7dca5a082006d026193f7c3e3a40a3a34a15348e Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Fri, 19 Sep 2014 23:23:31 +0100 Subject: [PATCH 28/45] Fix warnings in cbook Deprecation examples and a line continuation --- lib/matplotlib/cbook.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index f990e597682f..ee026c007739 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -122,9 +122,12 @@ def new_function(): Examples -------- - # To warn of the deprecation of "matplotlib.name_of_module" - warn_deprecated('1.4.0', name='matplotlib.name_of_module', - obj_type='module') + + Basic example:: + + # To warn of the deprecation of "matplotlib.name_of_module" + warn_deprecated('1.4.0', name='matplotlib.name_of_module', + obj_type='module') """ message = _generate_deprecation_message( @@ -174,9 +177,12 @@ def new_function(): Examples -------- - @deprecated('1.4.0') - def the_function_to_deprecate(): - pass + + Basic example:: + + @deprecated('1.4.0') + def the_function_to_deprecate(): + pass """ def deprecate(func, message=message, name=name, alternative=alternative, @@ -1911,7 +1917,7 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): General approach from: McGill, R., Tukey, J.W., and Larsen, W.A. (1978) "Variations of - Boxplots", The American Statistician, 32:12-16. + Boxplots", The American Statistician, 32:12-16. ''' From 52714d8138b8e99cf7b3eadca97663e6c44c2643 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Sat, 20 Sep 2014 00:09:56 +0100 Subject: [PATCH 29/45] Fix warnings in _axes Render other args of title better, etc --- lib/matplotlib/axes/_axes.py | 17 ++++++++++------- lib/matplotlib/pyplot.py | 8 +++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 32b565218ad0..ff539a798e77 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -124,9 +124,10 @@ def set_title(self, label, fontdict=None, loc="center", **kwargs): Other parameters ---------------- - Other keyword arguments are text properties, see - :class:`~matplotlib.text.Text` for a list of valid text - properties. + kwargs : text properties + Other keyword arguments are text properties, see + :class:`~matplotlib.text.Text` for a list of valid text + properties. """ try: title = {'left': self._left_title, @@ -3021,10 +3022,11 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, Returns ------- - A dictionary mapping each component of the boxplot - to a list of the :class:`matplotlib.lines.Line2D` - instances created. That dictionary has the following keys - (assuming vertical boxplots): + result : dict + A dictionary mapping each component of the boxplot + to a list of the :class:`matplotlib.lines.Line2D` + instances created. That dictionary has the following keys + (assuming vertical boxplots): - boxes: the main body of the boxplot showing the quartiles and the median's confidence intervals if enabled. @@ -5032,6 +5034,7 @@ def pcolormesh(self, *args, **kwargs): *edgecolors*: [*None* | ``'None'`` | ``'face'`` | color | color sequence] + If *None*, the rc setting is used by default. If ``'None'``, edges will not be visible. diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 5bd6488824e9..016a5c6b1a64 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1332,9 +1332,11 @@ def title(s, *args, **kwargs): Other parameters ---------------- - Other keyword arguments are text properties, see - :class:`~matplotlib.text.Text` for a list of valid text - properties. + kwargs : text properties + Other keyword arguments are text properties, see + :class:`~matplotlib.text.Text` for a list of valid text + properties. + See also -------- From f28c204d77c2bd033fc9396281748363ebcf6dac Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Sun, 21 Sep 2014 11:14:23 +0100 Subject: [PATCH 30/45] fix rendering of warnings in mlab --- lib/matplotlib/mlab.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/mlab.py b/lib/matplotlib/mlab.py index 17f85dd997c3..a2917b9e1dd1 100644 --- a/lib/matplotlib/mlab.py +++ b/lib/matplotlib/mlab.py @@ -541,9 +541,11 @@ def stride_windows(x, n, noverlap=None, axis=0): Get all windows of x with length n as a single array, using strides to avoid data duplication. - .. warning:: It is not safe to write to the output array. Multiple - elements may point to the same piece of memory, so modifying one value may - change others. + .. warning:: + + It is not safe to write to the output array. Multiple + elements may point to the same piece of memory, + so modifying one value may change others. Call signature:: @@ -603,9 +605,11 @@ def stride_repeat(x, n, axis=0): Repeat the values in an array in a memory-efficient manner. Array x is stacked vertically n times. - .. warning:: It is not safe to write to the output array. Multiple - elements may point to the same piece of memory, so modifying one value may - change others. + .. warning:: + + It is not safe to write to the output array. Multiple + elements may point to the same piece of memory, so + modifying one value may change others. Call signature:: @@ -1605,7 +1609,9 @@ def longest_ones(x): def prepca(P, frac=0): """ - WARNING: this function is deprecated -- please see class PCA instead + .. warning:: + + This function is deprecated -- please see class PCA instead Compute the principal components of *P*. *P* is a (*numVars*, *numObs*) array. *frac* is the minimum fraction of variance that a From 602176e128e0ae18659622a0e8ff61dfa8c3647b Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Sun, 21 Sep 2014 12:08:33 +0100 Subject: [PATCH 31/45] Improve rendering of title docs --- lib/matplotlib/pyplot.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 016a5c6b1a64..075ef8cb6944 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1311,17 +1311,24 @@ def title(s, *args, **kwargs): positioned above the axes in the center, flush with the left edge, and flush with the right edge. + .. seealso:: + See :func:`~matplotlib.pyplot.text` for adding text + to the current axes + Parameters ---------- label : str Text to use for the title + fontdict : dict A dictionary controlling the appearance of the title text, the default `fontdict` is: - {'fontsize': rcParams['axes.titlesize'], - 'fontweight' : rcParams['axes.titleweight'], - 'verticalalignment': 'baseline', - 'horizontalalignment': loc} + + {'fontsize': rcParams['axes.titlesize'], + 'fontweight' : rcParams['axes.titleweight'], + 'verticalalignment': 'baseline', + 'horizontalalignment': loc} + loc : {'center', 'left', 'right'}, str, optional Which title to set, defaults to 'center' @@ -1337,11 +1344,6 @@ def title(s, *args, **kwargs): :class:`~matplotlib.text.Text` for a list of valid text properties. - - See also - -------- - See :func:`~matplotlib.pyplot.text` for adding text to the current axes - """ l = gca().set_title(s, *args, **kwargs) draw_if_interactive() From 2f1fcde55f9bab4fc6b71c7bef725d1c321b32d0 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Sun, 21 Sep 2014 13:32:53 +0100 Subject: [PATCH 32/45] docstring in trirefine --- lib/matplotlib/tri/trirefine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/matplotlib/tri/trirefine.py b/lib/matplotlib/tri/trirefine.py index 11e3fff3e468..62a9f804ac1c 100644 --- a/lib/matplotlib/tri/trirefine.py +++ b/lib/matplotlib/tri/trirefine.py @@ -37,6 +37,7 @@ class TriRefiner(object): :class:`~matplotlib.tri.TriInterpolator` (optional) - the other optional keyword arguments *kwargs* are defined in each TriRefiner concrete implementation + and which returns (as a tuple) a refined triangular mesh and the interpolated values of the field at the refined triangulation nodes. From 3e4620ac3d407466f152eb6ef54518468ace2f4d Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 22 Sep 2014 10:41:54 +0100 Subject: [PATCH 33/45] Fixed firefox keyboard focus bug with nbagg backend. --- lib/matplotlib/backends/backend_webagg.py | 4 ++-- lib/matplotlib/backends/web_backend/nbagg_mpl.js | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_webagg.py b/lib/matplotlib/backends/backend_webagg.py index e2330780ca12..9c1a0e9a7f60 100644 --- a/lib/matplotlib/backends/backend_webagg.py +++ b/lib/matplotlib/backends/backend_webagg.py @@ -39,7 +39,7 @@ from matplotlib.figure import Figure from matplotlib._pylab_helpers import Gcf from . import backend_webagg_core as core -from . import backend_nbagg +from .backend_nbagg import TimerTornado def new_figure_manager(num, *args, **kwargs): @@ -103,7 +103,7 @@ def show(self): show() def new_timer(self, *args, **kwargs): - return backend_nbagg.TimerTornado(*args, **kwargs) + return TimerTornado(*args, **kwargs) def start_event_loop(self, timeout): backend_bases.FigureCanvasBase.start_event_loop_default( diff --git a/lib/matplotlib/backends/web_backend/nbagg_mpl.js b/lib/matplotlib/backends/web_backend/nbagg_mpl.js index 3232407fa8b0..82c020aa8264 100644 --- a/lib/matplotlib/backends/web_backend/nbagg_mpl.js +++ b/lib/matplotlib/backends/web_backend/nbagg_mpl.js @@ -55,6 +55,9 @@ mpl.figure.prototype.handle_close = function(fig, msg) { // Update the output cell to use the data from the current canvas. fig.push_to_output(); var dataURL = fig.canvas.toDataURL(); + // Re-enable the keyboard manager in IPython - without this line, in FF, + // the notebook keyboard shortcuts fail. + IPython.keyboard_manager.enable() $(fig.parent_element).html(''); fig.send_message('closing', {}); fig.ws.close() From 1b96117636aa9c67631ab97e9acde45bb69b48c5 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Mon, 22 Sep 2014 12:19:44 +0100 Subject: [PATCH 34/45] Updated nbagg backend to default to a transparent figure patch. --- lib/matplotlib/backends/backend_nbagg.py | 27 +++++++++++- .../backends/web_backend/nbagg_uat.ipynb | 41 +++++++++++++++++-- lib/matplotlib/rcsetup.py | 1 + matplotlibrc.template | 4 ++ 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 907c737419b5..231e6ce6bdc2 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -1,5 +1,6 @@ """Interactive figures in the IPython notebook""" from base64 import b64encode +from contextlib import contextmanager import json import io import os @@ -10,11 +11,14 @@ from IPython.display import display, Javascript, HTML from IPython.kernel.comm import Comm +from matplotlib import rcParams from matplotlib.figure import Figure +from matplotlib.backends import backend_agg from matplotlib.backends.backend_webagg_core import (FigureManagerWebAgg, FigureCanvasWebAggCore, NavigationToolbar2WebAgg) -from matplotlib.backend_bases import ShowBase, NavigationToolbar2, TimerBase +from matplotlib.backend_bases import (ShowBase, NavigationToolbar2, + TimerBase, FigureCanvasBase) class Show(ShowBase): @@ -179,6 +183,25 @@ class FigureCanvasNbAgg(FigureCanvasWebAggCore): def new_timer(self, *args, **kwargs): return TimerTornado(*args, **kwargs) + def start_event_loop(self, timeout): + FigureCanvasBase.start_event_loop_default(self, timeout) + + def stop_event_loop(self): + FigureCanvasBase.stop_event_loop_default(self) + + def draw(self): + renderer = self.get_renderer() + + self._png_is_old = True + + backend_agg.RendererAgg.lock.acquire() + try: + self.figure.draw(renderer) + finally: + backend_agg.RendererAgg.lock.release() + # Swap the frames + self.manager.refresh_all() + def new_figure_manager(num, *args, **kwargs): """ @@ -194,6 +217,8 @@ def new_figure_manager_given_figure(num, figure): Create a new figure manager instance for the given figure. """ canvas = FigureCanvasNbAgg(figure) + if rcParams['nbagg.transparent']: + figure.patch.set_alpha(0) manager = FigureManagerNbAgg(canvas, num) return manager diff --git a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb index 81e47b041e34..5f3407e68458 100644 --- a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb +++ b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb @@ -1,7 +1,7 @@ { "metadata": { "name": "", - "signature": "sha256:9a73a15660e6912c6bcfaf7c9e8247503738ce5d590a34e08789c2e16de93080" + "signature": "sha256:34c3a5a9d82d5a3c433f0c03f0717d43e1d14eaf78f88a64f094d104fa636ddc" }, "nbformat": 3, "nbformat_minor": 0, @@ -92,7 +92,7 @@ "cell_type": "code", "collapsed": false, "input": [ - "print matplotlib.backends.backend_nbagg.connection_info()" + "print(matplotlib.backends.backend_nbagg.connection_info())" ], "language": "python", "metadata": {}, @@ -354,9 +354,42 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### UAT ?? - Keyboard shortcuts in IPython after close of figure (Current known bug)\n", - "### UAT ?? - Race condition to show means that \"run all\" ends up collapsing some figures into one (Current known bug)" + "### UAT 14 - Keyboard shortcuts in IPython after close of figure\n", + "\n", + "After closing the previous figure (with the close button above the figure) the IPython keyboard shortcuts should still function.\n", + "\n", + "### UAT 15 - Figure face colours\n", + "\n", + "The nbagg honours all colours appart from that of the figure.patch. The two plots below should produce a figure with a transparent background and a red background respectively (check the transparency by closing the figure, and dragging the resulting image over other content). There should be no yellow figure." ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import matplotlib\n", + "matplotlib.rcParams.update({'figure.facecolor': 'red',\n", + " 'savefig.facecolor': 'yellow'})\n", + "plt.figure()\n", + "plt.plot([3, 2, 1])\n", + "\n", + "with matplotlib.rc_context({'nbagg.transparent': False}):\n", + " plt.figure()\n", + "\n", + "plt.plot([3, 2, 1])\n", + "plt.show()" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [], + "language": "python", + "metadata": {}, + "outputs": [] } ], "metadata": {} diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index f535aec084e6..e0bfb8b96395 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -485,6 +485,7 @@ def __call__(self, s): 'webagg.port': [8988, validate_int], 'webagg.open_in_browser': [True, validate_bool], 'webagg.port_retries': [50, validate_int], + 'nbagg.transparent': [True, validate_bool], 'toolbar': ['toolbar2', validate_toolbar], 'datapath': [None, validate_path_exists], # handled by # _get_data_path_cached diff --git a/matplotlibrc.template b/matplotlibrc.template index bec6ea7e4312..af7940a4ae67 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -57,6 +57,10 @@ backend : %(backend)s # When True, open the webbrowser to the plot that is shown # webagg.open_in_browser : True +# When True, the figures rendered in the nbagg backend are created with +# a transparent background. +# nbagg.transparent : True + # if you are running pyplot inside a GUI and your backend choice # conflicts, we will automatically try to find a compatible one for # you if backend_fallback is True From a62e7f3939e0268a7fd50e755619254b1231702c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 22 Sep 2014 11:54:40 -0400 Subject: [PATCH 35/45] Merge pull request #3545 from mdehoon/setfont_informative_error_message MNT : Provide an informative error message if something goes wrong in setfont --- src/_macosx.m | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/_macosx.m b/src/_macosx.m index aa326ef02483..ac4ff6a9f424 100644 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -2531,14 +2531,23 @@ static CGFloat _get_device_scale(CGContextRef cr) "CourierNewPS-Bold-ItalicMT"}, }; - if(!PyList_Check(family)) return 0; + if(!PyList_Check(family)) + { + PyErr_SetString(PyExc_ValueError, "family should be a list"); + return 0; + } n = PyList_GET_SIZE(family); for (i = 0; i < n; i++) { PyObject* item = PyList_GET_ITEM(family, i); ascii = PyUnicode_AsASCIIString(item); - if(!ascii) return 0; + if(!ascii) + { + PyErr_SetString(PyExc_ValueError, + "failed to convert font family name to ASCII"); + return 0; + } temp = PyBytes_AS_STRING(ascii); for (j = 0; j < NMAP; j++) { if (!strcmp(map[j].name, temp)) From b35fae63db2154a427326a4197a755a9e5790d2c Mon Sep 17 00:00:00 2001 From: Cimarron Mittelsteadt Date: Mon, 22 Sep 2014 16:10:28 -0700 Subject: [PATCH 36/45] DOC: Fixed comment documentation to match with code on timer interval set to 100 ms --- examples/event_handling/timers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/event_handling/timers.py b/examples/event_handling/timers.py index 0e293a84975c..4cd9351613fc 100644 --- a/examples/event_handling/timers.py +++ b/examples/event_handling/timers.py @@ -13,13 +13,13 @@ def update_title(axes): x = np.linspace(-3, 3) ax.plot(x, x*x) -# Create a new timer object. Set the interval 500 milliseconds (1000 is default) -# and tell the timer what function should be called. +# Create a new timer object. Set the interval to 100 milliseconds +# (1000 is default) and tell the timer what function should be called. timer = fig.canvas.new_timer(interval=100) timer.add_callback(update_title, ax) timer.start() -#Or could start the timer on first figure draw +# Or could start the timer on first figure draw #def start_timer(evt): # timer.start() # fig.canvas.mpl_disconnect(drawid) From 6872174ed924555189e1c6e93afc8deb0572c312 Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Sun, 21 Sep 2014 15:45:49 +0100 Subject: [PATCH 37/45] Install texlive and other dependencies when building docs. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 669fe3a8d4de..fefdf90e7aeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,8 @@ matrix: install: - pip install -q --use-mirrors nose python-dateutil numpy pep8 pyparsing pillow - sudo apt-get update && sudo apt-get -qq install inkscape libav-tools + # We use --no-install-recommends to avoid pulling in additional large latex docs that we don't need + - if [[ $BUILD_DOCS == true ]]; then sudo apt-get install -qq --no-install-recommends dvipng texlive-latex-base texlive-latex-extra texlive-fonts-recommended graphviz; fi - if [[ $BUILD_DOCS == true ]]; then pip install sphinx numpydoc linkchecker; fi - python setup.py install From aa583e4a114cb5883bf43d9d8d63c5323a898800 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Wed, 24 Sep 2014 11:08:03 +0100 Subject: [PATCH 38/45] Fixed the webagg backend to handle figures with transparency. --- lib/matplotlib/backends/backend_nbagg.py | 21 ++++-------- .../backends/backend_webagg_core.py | 33 +++++++++++-------- lib/matplotlib/backends/web_backend/mpl.js | 2 ++ .../backends/web_backend/nbagg_uat.ipynb | 14 +++----- 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 231e6ce6bdc2..540c4fd28400 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -1,4 +1,8 @@ """Interactive figures in the IPython notebook""" +# Note: There is a notebook in +# lib/matplotlib/backends/web_backend/nbagg_uat.ipynb to help verify +# that changes made maintain expected behaviour. + from base64 import b64encode from contextlib import contextmanager import json @@ -33,8 +37,8 @@ def __call__(self, block=None): for manager in managers: manager.show() - if not is_interactive() and manager in Gcf._activeQue: - Gcf._activeQue.remove(manager) + if not is_interactive() and manager in Gcf._activeQue: + Gcf._activeQue.remove(manager) show = Show() @@ -189,19 +193,6 @@ def start_event_loop(self, timeout): def stop_event_loop(self): FigureCanvasBase.stop_event_loop_default(self) - def draw(self): - renderer = self.get_renderer() - - self._png_is_old = True - - backend_agg.RendererAgg.lock.acquire() - try: - self.figure.draw(renderer) - finally: - backend_agg.RendererAgg.lock.release() - # Swap the frames - self.manager.refresh_all() - def new_figure_manager(num, *args, **kwargs): """ diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index eee727dbc7c7..3aa01724932c 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -71,7 +71,7 @@ def show(self): show() def draw(self): - renderer = self.get_renderer() + renderer = self.get_renderer(cleared=True) self._png_is_old = True @@ -91,21 +91,25 @@ def get_diff_image(self): # The buffer is created as type uint32 so that entire # pixels can be compared in one numpy call, rather than # needing to compare each plane separately. - buff = np.frombuffer( - self.get_renderer().buffer_rgba(), dtype=np.uint32) - buff.shape = ( - self._renderer.height, self._renderer.width) + renderer = self.get_renderer() + buff = np.frombuffer(renderer.buffer_rgba(), dtype=np.uint32) - if not self._force_full: - last_buffer = np.frombuffer( - self._last_renderer.buffer_rgba(), dtype=np.uint32) - last_buffer.shape = ( - self._renderer.height, self._renderer.width) + buff.shape = (renderer.height, renderer.width) + + # If any pixels have transparency, we need to force a full draw + # as we cannot overlay new on top of old. + pixels = buff.view(dtype=np.uint8).reshape(buff.shape + (4,)) + some_transparency = np.any(pixels[:, :, 3] != 255) + + output = buff + + if not self._force_full and not some_transparency: + last_buffer = np.frombuffer(self._last_renderer.buffer_rgba(), + dtype=np.uint32) + last_buffer.shape = (renderer.height, renderer.width) diff = buff != last_buffer output = np.where(diff, buff, 0) - else: - output = buff # Clear out the PNG data buffer rather than recreating it # each time. This reduces the number of memory @@ -122,7 +126,7 @@ def get_diff_image(self): # Swap the renderer frames self._renderer, self._last_renderer = ( - self._last_renderer, self._renderer) + self._last_renderer, renderer) self._force_full = False self._png_is_old = False return self._png_buffer.getvalue() @@ -147,6 +151,9 @@ def get_renderer(self, cleared=None): w, h, self.figure.dpi) self._lastKey = key + elif cleared: + self._renderer.clear() + return self._renderer def handle_event(self, event): diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index 6f1cf79364cb..5856b2347e32 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -60,6 +60,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { } this.imageObj.onload = function() { + fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height); fig.context.drawImage(fig.imageObj, 0, 0); fig.waiting = false; }; @@ -322,6 +323,7 @@ mpl.figure.prototype._make_on_message_function = function(fig) { (window.URL || window.webkitURL).revokeObjectURL( fig.imageObj.src); } + fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL( evt.data); fig.updated_canvas_event(); diff --git a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb index 5f3407e68458..3601c1490643 100644 --- a/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb +++ b/lib/matplotlib/backends/web_backend/nbagg_uat.ipynb @@ -1,7 +1,7 @@ { "metadata": { "name": "", - "signature": "sha256:34c3a5a9d82d5a3c433f0c03f0717d43e1d14eaf78f88a64f094d104fa636ddc" + "signature": "sha256:1d491e506b54b126f6971897d3249a9e6f96f9d50bf4e4ba8d179c6b7b1aefa8" }, "nbformat": 3, "nbformat_minor": 0, @@ -24,7 +24,6 @@ "import matplotlib\n", "reload(matplotlib)\n", "\n", - "\n", "matplotlib.use('nbagg')\n", "\n", "import matplotlib.backends.backend_nbagg\n", @@ -47,6 +46,9 @@ "cell_type": "code", "collapsed": false, "input": [ + "import matplotlib.backends.backend_webagg_core\n", + "reload(matplotlib.backends.backend_webagg_core)\n", + "\n", "import matplotlib.pyplot as plt\n", "plt.interactive(False)\n", "\n", @@ -382,14 +384,6 @@ "language": "python", "metadata": {}, "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", - "metadata": {}, - "outputs": [] } ], "metadata": {} From d37e613cbe114975268198145aac51dfdbce0385 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 24 Sep 2014 09:03:22 -0400 Subject: [PATCH 39/45] PEP8 : whitespace fix --- lib/matplotlib/backends/backend_webagg_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 3aa01724932c..526de22089b8 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -97,7 +97,7 @@ def get_diff_image(self): buff.shape = (renderer.height, renderer.width) # If any pixels have transparency, we need to force a full draw - # as we cannot overlay new on top of old. + # as we cannot overlay new on top of old. pixels = buff.view(dtype=np.uint8).reshape(buff.shape + (4,)) some_transparency = np.any(pixels[:, :, 3] != 255) From 2775a7f346c626c34508ee39f001c05266ff96a1 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Sep 2014 08:49:19 -0400 Subject: [PATCH 40/45] BUG : don't assume label in boxpplot_stat If the user does not pass in a label, do not assign one. Fixes #3563 --- lib/matplotlib/cbook.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 4cd1fcca9afe..56d68aa47041 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -10,7 +10,8 @@ unicode_literals) import six -from six.moves import xrange +from six.moves import xrange, zip +from itertools import repeat import datetime import errno @@ -1948,14 +1949,15 @@ def _compute_conf_interval(data, med, iqr, bootstrap): ncols = len(X) if labels is None: - labels = [str(i) for i in range(1, ncols+1)] + labels = repeat(None) elif len(labels) != ncols: raise ValueError("Dimensions of labels and X must be compatible") for ii, (x, label) in enumerate(zip(X, labels), start=0): # empty dict stats = {} - stats['label'] = label + if label is not None: + stats['label'] = label # arithmetic mean stats['mean'] = np.mean(x) From b30d50d31dc0f1c1e6633bd3e3e5a1d47427c04e Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 25 Sep 2014 14:47:49 +0100 Subject: [PATCH 41/45] Fixed the differencing of images for the webagg/nbagg backends. --- lib/matplotlib/backends/backend_nbagg.py | 12 +++++- .../backends/backend_webagg_core.py | 43 +++++++++++++++---- lib/matplotlib/backends/web_backend/mpl.js | 13 +++++- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 32c9ebf988a5..bea523622454 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -35,12 +35,20 @@ def __call__(self, block=None): if not managers: return + interactive = is_interactive() + for manager in managers: manager.show() - if not is_interactive() and manager in Gcf._activeQue: - Gcf._activeQue.remove(manager) + # plt.figure adds an event which puts the figure in focus + # in the activeQue. Disable this behaviour, as it results in + # figures being put as the active figure after they have been + # shown, even in non-interactive mode. + if hasattr(manager, '_cidgcf'): + manager.canvas.mpl_disconnect(manager._cidgcf) + if not interactive and manager in Gcf._activeQue: + Gcf._activeQue.remove(manager) show = Show() diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 31b641ac0982..dfd77ec450c8 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -65,6 +65,12 @@ def __init__(self, *args, **kwargs): # sent to the clients will be a full frame. self._force_full = True + # Store the current image mode so that at any point, clients can + # request the information. This should be changed by calling + # self.set_image_mode(mode) so that the notification can be given + # to the connected clients. + self._current_image_mode = 'full' + def show(self): # show the figure window from matplotlib.pyplot import show @@ -86,24 +92,41 @@ def draw(self): def draw_idle(self): self.send_event("draw") + def set_image_mode(self, mode): + """ + Set the image mode for any subsequent images which will be sent + to the clients. The modes may currently be either 'full' or 'diff'. + + Note: diff images may not contain transparency, therefore upon + draw this mode may be changed if the resulting image has any + transparent component. + + """ + if mode not in ['full', 'diff']: + raise ValueError('image mode must be either full or diff.') + if self._current_image_mode != mode: + self._current_image_mode = mode + self.handle_send_image_mode(None) + def get_diff_image(self): if self._png_is_old: + renderer = self.get_renderer() + # The buffer is created as type uint32 so that entire # pixels can be compared in one numpy call, rather than # needing to compare each plane separately. - renderer = self.get_renderer() buff = np.frombuffer(renderer.buffer_rgba(), dtype=np.uint32) - buff.shape = (renderer.height, renderer.width) - # If any pixels have transparency, we need to force a full draw - # as we cannot overlay new on top of old. + # If any pixels have transparency, we need to force a full + # draw as we cannot overlay new on top of old. pixels = buff.view(dtype=np.uint8).reshape(buff.shape + (4,)) - some_transparency = np.any(pixels[:, :, 3] != 255) - - output = buff - if not self._force_full and not some_transparency: + if self._force_full or np.any(pixels[:, :, 3] != 255): + self.set_image_mode('full') + output = buff + else: + self.set_image_mode('diff') last_buffer = np.frombuffer(self._last_renderer.buffer_rgba(), dtype=np.uint32) last_buffer.shape = (renderer.height, renderer.width) @@ -230,6 +253,10 @@ def handle_resize(self, event): self._png_is_old = True self.manager.resize(w, h) + def handle_send_image_mode(self, event): + # The client requests notification of what the current image mode is. + self.send_event('image_mode', mode=self._current_image_mode) + def send_event(self, event_type, **kwargs): self.manager._send_event(event_type, **kwargs) diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index 5856b2347e32..6d6a1f3a4e59 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -41,6 +41,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { this.format_dropdown = undefined; this.focus_on_mousover = false; + this.image_mode = 'full'; this.root = $('
'); this.root.attr('style', 'display: inline-block'); @@ -56,11 +57,17 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { this.ws.onopen = function () { fig.send_message("supports_binary", {value: fig.supports_binary}); + fig.send_message("send_image_mode", {}); fig.send_message("refresh", {}); } this.imageObj.onload = function() { - fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height); + if (fig.image_mode == 'full') { + // Full images could contain transparency (where diff images + // almost always do), so we need to clear the canvas so that + // there is no ghosting. + fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height); + } fig.context.drawImage(fig.imageObj, 0, 0); fig.waiting = false; }; @@ -302,6 +309,10 @@ mpl.figure.prototype.handle_draw = function(fig, msg) { fig.send_draw_message(); } +mpl.figure.prototype.handle_image_mode = function(fig, msg) { + fig.image_mode = msg['mode']; +} + mpl.figure.prototype.updated_canvas_event = function() { // Called whenever the canvas gets updated. this.send_message("ack", {}); From 236355c3a861b20e8d30965fce3b39383699e1dc Mon Sep 17 00:00:00 2001 From: William Manley Date: Fri, 19 Sep 2014 16:52:15 +0100 Subject: [PATCH 42/45] pyplot: Fix exception in `_backend_selection` during import if the new style introspection GObject python bindings are in use. With pygobject >= 3.13.4 the following: from gi.repository import GObject from matplotlib import pyplot causes an exception to be raised: > AttributeError: When using gi.repository you must not import static modules > like "gobject". Please change all occurrences of "import gobject" to "from > gi.repository import GObject". See: > https://bugzilla.gnome.org/show_bug.cgi?id=709183 It is not valid to use both non-introspection based and introspection based PyGObject in the same process. Backend probing will `import gobject` (i.e. the non-introspection bindings) if it sees that the 'gtk' module is loaded. Unfortunately it wouldn't check if this was the pygi or old-style gtk module. This commit adds this check avoiding the exception. This check was added to PyGObject in [d704033][1] [1]: https://git.gnome.org/browse/pygobject/commit/?id=d704033 --- lib/matplotlib/pyplot.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 075ef8cb6944..fa2a78edc5ea 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -91,8 +91,9 @@ def _backend_selection(): if not PyQt5.QtWidgets.qApp.startingUp(): # The mainloop is running. rcParams['backend'] = 'qt5Agg' - elif 'gtk' in sys.modules and not backend in ('GTK', 'GTKAgg', - 'GTKCairo'): + elif ('gtk' in sys.modules + and backend not in ('GTK', 'GTKAgg', 'GTKCairo') + and 'gi.repository.GObject' not in sys.modules): import gobject if gobject.MainLoop().is_running(): rcParams['backend'] = 'gtk' + 'Agg' * is_agg_backend From 6bcffbe48e1f83f57e499a88f23d9b444e89d5c8 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 26 Sep 2014 22:28:55 -0400 Subject: [PATCH 43/45] TST : modified tests to pass - added check that labels don't get adding by default --- lib/matplotlib/tests/test_cbook.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index d41044c004a4..61d69fd76efd 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -121,8 +121,7 @@ def setup(self): 'q1': 1.3597529879465153, 'q3': 14.85246294739361, 'whishi': 27.899688243699629, - 'whislo': 0.042143774965502923, - 'label': 1 + 'whislo': 0.042143774965502923 } self.known_bootstrapped_ci = { @@ -136,10 +135,6 @@ def setup(self): 'fliers': np.array([92.55467075, 87.03819018]), } - self.known_res_with_labels = { - 'label': 'Test1' - } - self.known_res_percentiles = { 'whislo': 0.1933685896907924, 'whishi': 42.232049135969874 @@ -229,11 +224,15 @@ def test_results_whiskers_percentiles(self): ) def test_results_withlabels(self): - labels = ['Test1', 2, 3, 4] + labels = ['Test1', 2, 'ardvark', 4] results = cbook.boxplot_stats(self.data, labels=labels) res = results[0] - for key in list(self.known_res_with_labels.keys()): - assert_equal(res[key], self.known_res_with_labels[key]) + for lab, res in zip(labels, results): + assert_equal(res['label'], lab) + + results = cbook.boxplot_stats(self.data) + for res in results: + assert('label' not in res) @raises(ValueError) def test_label_error(self): From 7602e3da2d8c1e3a0b00dd602352567e4f05e9c4 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 26 Sep 2014 22:57:36 -0400 Subject: [PATCH 44/45] BUG : deal with empty list passed to boxplot If a data list is empty, return a dict full of np.nan. Closes #3569 and addresses part of https://github.com/pydata/pandas/issues/8382 --- lib/matplotlib/cbook.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index ee026c007739..5a15d8771817 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1896,7 +1896,7 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): ======== =================================== label tick label for the boxplot mean arithemetic mean value - median 50th percentile + med 50th percentile q1 first quartile (25th percentile) q3 third quartile (75th percentile) cilo lower notch around the median @@ -1962,6 +1962,7 @@ def _compute_conf_interval(data, med, iqr, bootstrap): input_whis = whis for ii, (x, label) in enumerate(zip(X, labels), start=0): + # empty dict stats = {} stats['label'] = label @@ -1969,6 +1970,26 @@ def _compute_conf_interval(data, med, iqr, bootstrap): # restore whis to the input values in case it got changed in the loop whis = input_whis + # note tricksyness, append up here and then mutate below + bxpstats.append(stats) + + # if empty, bail + if len(x) == 0: + stats['fliers'] = np.array([]) + stats['mean'] = np.nan + stats['med'] = np.nan + stats['q1'] = np.nan + stats['q3'] = np.nan + stats['cilo'] = np.nan + stats['ciho'] = np.nan + stats['whislo'] = np.nan + stats['whishi'] = np.nan + stats['med'] = np.nan + continue + + # up-convert to an array, just to be safe + x = np.asarray(x) + # arithmetic mean stats['mean'] = np.mean(x) @@ -2021,9 +2042,9 @@ def _compute_conf_interval(data, med, iqr, bootstrap): np.compress(x > stats['whishi'], x) ]) - # add in teh remaining stats and append to final output + # add in the remaining stats stats['q1'], stats['med'], stats['q3'] = q1, med, q3 - bxpstats.append(stats) + return bxpstats From 5e2b03c1412f33f66b8304ef287c0fbab5ca943b Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 29 Sep 2014 10:59:53 -0400 Subject: [PATCH 45/45] Fix crash in picking for zero-length path collection --- src/_path.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/_path.cpp b/src/_path.cpp index 27bb4b563fc2..783aee0654f9 100644 --- a/src/_path.cpp +++ b/src/_path.cpp @@ -770,7 +770,14 @@ _path_module::point_in_path_collection(const Py::Tuple& args) throw Py::ValueError("Offsets array must be Nx2"); } + Py::List result; + size_t Npaths = paths.length(); + + if (Npaths == 0) { + return result; + } + size_t Noffsets = offsets->dimensions[0]; size_t N = std::max(Npaths, Noffsets); size_t Ntransforms = std::min(transforms_obj.length(), N); @@ -788,7 +795,6 @@ _path_module::point_in_path_collection(const Py::Tuple& args) transforms.push_back(trans); } - Py::List result; agg::trans_affine trans; for (i = 0; i < N; ++i)