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)