From bf6c07138e27c5f3a5ee06eb8c7cd6cf946abea4 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sat, 10 Dec 2016 19:57:58 +0000
Subject: [PATCH 1/3] Raise warning when size_index set to non-numeric
dimension
---
holoviews/plotting/bokeh/chart.py | 15 ++++++---
holoviews/plotting/mpl/chart.py | 15 ++++++---
tests/testplotinstantiation.py | 51 ++++++++++++++++++++++++++++++-
3 files changed, 72 insertions(+), 9 deletions(-)
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index 3c18ad13eb..caa7c493fc 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -67,15 +67,22 @@ def get_data(self, element, ranges=None, empty=False):
sdim = element.get_dimension(self.size_index)
if sdim:
map_key = 'size_' + sdim.name
- mapping['size'] = map_key
if empty:
data[map_key] = []
+ mapping['size'] = map_key
else:
ms = style.get('size', np.sqrt(6))**2
sizes = element.dimension_values(self.size_index)
- data[map_key] = np.sqrt(compute_sizes(sizes, self.size_fn,
- self.scaling_factor,
- self.scaling_method, ms))
+ if sizes.dtype.kind in ('i', 'f'):
+ sizes = compute_sizes(sizes, self.size_fn,
+ self.scaling_factor,
+ self.scaling_method, ms)
+ data[map_key] = np.sqrt(sizes)
+ mapping['size'] = map_key
+ else:
+ eltype = type(element).__name__
+ self.warning('%s dimension is not numeric, cannot '
+ 'use to scale %s size.' % (sdim, eltype))
data[dims[xidx]] = [] if empty else element.dimension_values(xidx)
data[dims[yidx]] = [] if empty else element.dimension_values(yidx)
diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py
index a89624d4bb..4e64916dc3 100644
--- a/holoviews/plotting/mpl/chart.py
+++ b/holoviews/plotting/mpl/chart.py
@@ -507,11 +507,18 @@ def _compute_styles(self, element, ranges, style):
style['c'] = color
style['edgecolors'] = style.pop('edgecolors', style.pop('edgecolor', 'none'))
- if element.get_dimension(self.size_index):
+ sdim = element.get_dimension(self.size_index)
+ if sdim:
sizes = element.dimension_values(self.size_index)
- ms = style.pop('s') if 's' in style else plt.rcParams['lines.markersize']
- style['s'] = compute_sizes(sizes, self.size_fn, self.scaling_factor,
- self.scaling_method, ms)
+ if sizes.dtype.kind in ('i', 'f'):
+ style['s'] = compute_sizes(sizes, self.size_fn, self.scaling_factor,
+ self.scaling_method, ms)
+ ms = style.pop('s') if 's' in style else plt.rcParams['lines.markersize']
+ else:
+ eltype = type(element).__name__
+ self.warning('%s dimension is not numeric, cannot '
+ 'use to scale %s size.' % (sdim, eltype))
+
style['edgecolors'] = style.pop('edgecolors', 'none')
diff --git a/tests/testplotinstantiation.py b/tests/testplotinstantiation.py
index 59c364a226..dfc1e9c981 100644
--- a/tests/testplotinstantiation.py
+++ b/tests/testplotinstantiation.py
@@ -1,11 +1,14 @@
"""
Tests of plot instantiation (not display tests, just instantiation)
"""
+from __future__ import unicode_literals
+import logging
from collections import deque
from unittest import SkipTest
-from io import BytesIO
+from io import BytesIO, StringIO
+import param
import numpy as np
from holoviews import (Dimension, Overlay, DynamicMap, Store,
NdOverlay, GridSpace)
@@ -41,6 +44,29 @@
plotly_renderer = None
+class ParamLogStream(object):
+ """
+ Context manager that replaces the param logger and captures
+ log messages in a StringIO stream.
+ """
+
+ def __enter__(self):
+ self.stream = StringIO()
+ self._handler = logging.StreamHandler(self.stream)
+ self._logger = logging.getLogger('testlogger')
+ for handler in self._logger.handlers:
+ self._logger.removeHandler(handler)
+ self._logger.addHandler(self._handler)
+ self._param_logger = param.parameterized.logger
+ param.parameterized.logger = self._logger
+ return self
+
+ def __exit__(self, *args):
+ param.parameterized.logger = self._param_logger
+ self._handler.close()
+ self.stream.seek(0)
+
+
class TestMPLPlotInstantiation(ComparisonTestCase):
def setUp(self):
@@ -103,6 +129,17 @@ def history_callback(x, history=deque(maxlen=10)):
self.assertEqual(x, np.arange(10))
self.assertEqual(y, np.arange(10, 20))
+ def test_points_non_numeric_size_warning(self):
+ data = (range(10), range(10), map(chr, range(94,104)))
+ points = Points(data, vdims=['z'])(plot=dict(size_index=2))
+ with ParamLogStream() as log:
+ plot = mpl_renderer.get_plot(points)
+ log_msg = log.stream.read()
+ warning = ('%s: z dimension is not numeric, '
+ 'cannot use to scale Points size.\n' % plot.name)
+ self.assertEqual(log_msg, warning)
+
+
class TestBokehPlotInstantiation(ComparisonTestCase):
@@ -245,6 +282,18 @@ def test_image_boolean_array(self):
self.assertEqual(source.data['image'][0],
np.array([[0, 1], [1, 0]]))
+ def test_points_non_numeric_size_warning(self):
+ data = (range(10), range(10), map(chr, range(94,104)))
+ points = Points(data, vdims=['z'])(plot=dict(size_index=2))
+ with ParamLogStream() as log:
+ plot = bokeh_renderer.get_plot(points)
+ log_msg = log.stream.read()
+ warning = ('%s: z dimension is not numeric, '
+ 'cannot use to scale Points size.\n' % plot.name)
+ self.assertEqual(log_msg, warning)
+
+
+
class TestPlotlyPlotInstantiation(ComparisonTestCase):
def setUp(self):
From dfb8bb7397af51ca8800411d6753290ce64e7542 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sat, 10 Dec 2016 20:16:26 +0000
Subject: [PATCH 2/3] Python3 fix in test
---
tests/testplotinstantiation.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/testplotinstantiation.py b/tests/testplotinstantiation.py
index dfc1e9c981..a9b1d9f891 100644
--- a/tests/testplotinstantiation.py
+++ b/tests/testplotinstantiation.py
@@ -130,7 +130,7 @@ def history_callback(x, history=deque(maxlen=10)):
self.assertEqual(y, np.arange(10, 20))
def test_points_non_numeric_size_warning(self):
- data = (range(10), range(10), map(chr, range(94,104)))
+ data = (np.arange(10), np.arange(10), list(map(chr, range(94,104))))
points = Points(data, vdims=['z'])(plot=dict(size_index=2))
with ParamLogStream() as log:
plot = mpl_renderer.get_plot(points)
@@ -283,7 +283,7 @@ def test_image_boolean_array(self):
np.array([[0, 1], [1, 0]]))
def test_points_non_numeric_size_warning(self):
- data = (range(10), range(10), map(chr, range(94,104)))
+ data = (np.arange(10), np.arange(10), list(map(chr, range(94,104))))
points = Points(data, vdims=['z'])(plot=dict(size_index=2))
with ParamLogStream() as log:
plot = bokeh_renderer.get_plot(points)
From 0970453b2ca78a3aa0c0071631adca0b451ff1f7 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sun, 11 Dec 2016 16:37:18 +0000
Subject: [PATCH 3/3] Slightly refactored compute_sizes utility
---
holoviews/plotting/bokeh/chart.py | 14 +++++++-------
holoviews/plotting/mpl/chart.py | 12 ++++++------
holoviews/plotting/util.py | 2 ++
3 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index caa7c493fc..b242ac08e1 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -73,16 +73,16 @@ def get_data(self, element, ranges=None, empty=False):
else:
ms = style.get('size', np.sqrt(6))**2
sizes = element.dimension_values(self.size_index)
- if sizes.dtype.kind in ('i', 'f'):
- sizes = compute_sizes(sizes, self.size_fn,
- self.scaling_factor,
- self.scaling_method, ms)
- data[map_key] = np.sqrt(sizes)
- mapping['size'] = map_key
- else:
+ sizes = compute_sizes(sizes, self.size_fn,
+ self.scaling_factor,
+ self.scaling_method, ms)
+ if sizes is None:
eltype = type(element).__name__
self.warning('%s dimension is not numeric, cannot '
'use to scale %s size.' % (sdim, eltype))
+ else:
+ data[map_key] = np.sqrt(sizes)
+ mapping['size'] = map_key
data[dims[xidx]] = [] if empty else element.dimension_values(xidx)
data[dims[yidx]] = [] if empty else element.dimension_values(yidx)
diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py
index 4e64916dc3..3cfe8eccb5 100644
--- a/holoviews/plotting/mpl/chart.py
+++ b/holoviews/plotting/mpl/chart.py
@@ -510,15 +510,15 @@ def _compute_styles(self, element, ranges, style):
sdim = element.get_dimension(self.size_index)
if sdim:
sizes = element.dimension_values(self.size_index)
- if sizes.dtype.kind in ('i', 'f'):
- style['s'] = compute_sizes(sizes, self.size_fn, self.scaling_factor,
- self.scaling_method, ms)
- ms = style.pop('s') if 's' in style else plt.rcParams['lines.markersize']
- else:
+ ms = style['s'] if 's' in style else plt.rcParams['lines.markersize']
+ sizes = compute_sizes(sizes, self.size_fn, self.scaling_factor,
+ self.scaling_method, ms)
+ if sizes is None:
eltype = type(element).__name__
self.warning('%s dimension is not numeric, cannot '
'use to scale %s size.' % (sdim, eltype))
-
+ else:
+ style['s'] = sizes
style['edgecolors'] = style.pop('edgecolors', 'none')
diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py
index 80f6e4ea4f..e8ea200513 100644
--- a/holoviews/plotting/util.py
+++ b/holoviews/plotting/util.py
@@ -98,6 +98,8 @@ def compute_sizes(sizes, size_fn, scaling_factor, scaling_method, base_size):
base size and size_fn, which will be applied before
scaling.
"""
+ if sizes.dtype.kind not in ('i', 'f'):
+ return None
if scaling_method == 'area':
pass
elif scaling_method == 'width':