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':