From ff38645f979079040291ffecb347e81f10939b62 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 7 Apr 2017 12:05:02 +0100 Subject: [PATCH 1/4] A number of small plotting fixes --- holoviews/plotting/bokeh/chart.py | 18 +++++++++++++++--- holoviews/plotting/bokeh/path.py | 1 + holoviews/plotting/bokeh/util.py | 18 ++++++++++++------ holoviews/plotting/mpl/element.py | 2 +- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index b9fcb49bbf..922a687d0f 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -258,6 +258,7 @@ def get_batched_data(self, overlay, ranges=None, empty=False): styles = styles.max_cycles(len(self.ordering)) for (key, el), zorder in zip(overlay.data.items(), zorders): + self.set_param(**self.lookup_options(el, 'plot').options) eldata, elmapping = self.get_data(el, ranges, empty) for k, eld in eldata.items(): data[k].append(eld) @@ -490,7 +491,18 @@ class SpikesPlot(PathPlot, ColorbarPlot): def get_extents(self, element, ranges): l, b, r, t = super(SpikesPlot, self).get_extents(element, ranges) if len(element.dimensions()) == 1: - b, t = self.position, self.position+self.spike_length + if self.batched: + bs, ts = [], [] + for el in self.current_frame.values(): + opts = self.lookup_options(el, 'plot').options + pos = opts.get('position', self.position) + length = opts.get('spike_length', self.spike_length) + bs.append(pos) + ts.append(pos+length) + b = np.nanmin(bs) + t = np.nanmax(ts) + else: + b, t = self.position, self.position+self.spike_length else: b = np.nanmin([0, b]) t = np.nanmax([0, t]) @@ -505,11 +517,11 @@ def get_data(self, element, ranges=None, empty=False): if empty: xs, ys = [], [] elif len(dims) > 1: - xs, ys = zip(*(((x, x), (pos+y, pos)) + xs, ys = zip(*((np.array([x, x]), np.array([pos+y, pos])) for x, y in element.array(dims[:2]))) else: height = self.spike_length - xs, ys = zip(*(((x[0], x[0]), (pos+height, pos)) + xs, ys = zip(*((np.array([x[0], x[0]]), np.array([pos+height, pos])) for x in element.array(dims[:1]))) if not empty and self.invert_axes: xs, ys = ys, xs diff --git a/holoviews/plotting/bokeh/path.py b/holoviews/plotting/bokeh/path.py index d8a5ca79c1..9c27527cbc 100644 --- a/holoviews/plotting/bokeh/path.py +++ b/holoviews/plotting/bokeh/path.py @@ -43,6 +43,7 @@ def get_batched_data(self, element, ranges=None, empty=False): styles = styles.max_cycles(len(self.ordering)) for (key, el), zorder in zip(element.data.items(), zorders): + self.set_param(**self.lookup_options(el, 'plot').options) self.overlay_dims = dict(zip(element.kdims, key)) eldata, elmapping = self.get_data(el, ranges, empty) for k, eld in eldata.items(): diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py index 07f3f7f67f..ec7e993066 100644 --- a/holoviews/plotting/bokeh/util.py +++ b/holoviews/plotting/bokeh/util.py @@ -27,7 +27,7 @@ from ...core.options import abbreviated_exception from ...core.overlay import Overlay -from ...core.util import basestring +from ...core.util import basestring, unique_array from ..util import dim_axis_label @@ -567,7 +567,7 @@ def expand_batched_style(style, opts, mapping, nvals): alias = 'alpha' else: alias = None - if opt not in style: + if opt not in style or opt in mapping: continue elif opt == alias: if alias in applied_styles: @@ -595,14 +595,20 @@ def expand_batched_style(style, opts, mapping, nvals): def filter_batched_data(data, mapping): """ Iterates over the data and mapping for a ColumnDataSource and - replaces columns with repeating values with scalar + replaces columns with repeating values with a scalar. This is + purely and optimization for scalar types. """ for k, v in list(mapping.items()): if isinstance(v, dict) and 'field' in v: + if 'transform' in v: + continue v = v['field'] elif not isinstance(v, basestring): continue values = data[v] - if len(np.unique(values)) == 1: - mapping[k] = values[0] - del data[v] + try: + if len(unique_array(values)) == 1: + mapping[k] = values[0] + del data[v] + except: + pass diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index e72b151759..7a9d47f0d1 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -664,7 +664,7 @@ def _norm_kwargs(self, element, ranges, opts, vdim): opts['vmax'] = clim[1] # Check whether the colorbar should indicate clipping - values = element.dimension_values(vdim) + values = np.asarray(element.dimension_values(vdim)) if values.dtype.kind not in 'OSUM': el_min, el_max = np.nanmin(values), np.nanmax(values) else: From 4bf84858fc2537e6e14fbb888f160c60e4f1e582 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 7 Apr 2017 13:04:38 +0100 Subject: [PATCH 2/4] Added a number of unit tests for plotting --- tests/testplotinstantiation.py | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/testplotinstantiation.py b/tests/testplotinstantiation.py index c86b8996b6..e55382e542 100644 --- a/tests/testplotinstantiation.py +++ b/tests/testplotinstantiation.py @@ -288,6 +288,15 @@ def test_points_rcparams_used(self): lines = ax.get_xgridlines() self.assertEqual(lines[0].get_color(), 'red') + def test_polygons_colored(self): + polygons = NdOverlay({j: Polygons([[(i**j, i) for i in range(10)]], level=j) + for j in range(5)}) + plot = mpl_renderer.get_plot(polygons) + for j, splot in enumerate(plot.subplots.values()): + artist = splot.handles['artist'] + self.assertEqual(artist.get_array(), np.array([j])) + self.assertEqual(artist.get_clim(), (0, 4)) + class TestBokehPlotInstantiation(ComparisonTestCase): @@ -328,6 +337,15 @@ def test_batched_plot(self): extents = plot.get_extents(overlay, {}) self.assertEqual(extents, (0, 0, 98, 98)) + def test_batched_spike_plot(self): + overlay = NdOverlay({i: Spikes([i], kdims=['Time'])(plot=dict(position=0.1*i, + spike_length=0.1, + show_legend=False)) + for i in range(10)}) + plot = bokeh_renderer.get_plot(overlay) + extents = plot.get_extents(overlay, {}) + self.assertEqual(extents, (0, 0, 9, 1)) + def test_batched_points_size_and_color(self): opts = {'NdOverlay': dict(plot=dict(legend_limit=0)), 'Points': dict(style=dict(size=Cycle(values=[1, 2])))} @@ -506,6 +524,27 @@ def _test_colormapping(self, element, dim, log=False): mapper_type = LogColorMapper if log else LinearColorMapper self.assertTrue(isinstance(cmapper, mapper_type)) + def test_polygons_colored(self): + polygons = NdOverlay({j: Polygons([[(i**j, i) for i in range(10)]], level=j) + for j in range(5)}) + plot = bokeh_renderer.get_plot(polygons) + for i, splot in enumerate(plot.subplots.values()): + cmapper = splot.handles['color_mapper'] + self.assertEqual(cmapper.low, 0) + self.assertEqual(cmapper.high, 4) + source = splot.handles['source'] + self.assertEqual(source.data['Value'], np.array([i])) + + def test_polygons_colored_batched(self): + polygons = NdOverlay({j: Polygons([[(i**j, i) for i in range(10)]], level=j) + for j in range(5)})(plot=dict(legend_limit=0)) + plot = list(bokeh_renderer.get_plot(polygons).subplots.values())[0] + cmapper = plot.handles['color_mapper'] + self.assertEqual(cmapper.low, 0) + self.assertEqual(cmapper.high, 4) + source = plot.handles['source'] + self.assertEqual(source.data['Value'], list(range(5))) + def test_points_colormapping(self): points = Points(np.random.rand(10, 4), vdims=['a', 'b'])(plot=dict(color_index=3)) self._test_colormapping(points, 3) From 7155cdcc8b80f9264923312fedaaaaf0afc5bf78 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 7 Apr 2017 13:35:11 +0100 Subject: [PATCH 3/4] Small bugfixes for Polygon colormapping --- holoviews/plotting/bokeh/path.py | 5 +++-- tests/testplotinstantiation.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/bokeh/path.py b/holoviews/plotting/bokeh/path.py index 9c27527cbc..6e8644e057 100644 --- a/holoviews/plotting/bokeh/path.py +++ b/holoviews/plotting/bokeh/path.py @@ -87,9 +87,10 @@ def get_data(self, element, ranges=None, empty=False): if element.vdims and element.level is not None: cdim = element.vdims[0] + dim_name = util.dimension_sanitizer(cdim.name) cmapper = self._get_colormapper(cdim, element, ranges, style) - data[cdim.name] = [] if empty else element.dimension_values(2) - mapping['fill_color'] = {'field': cdim.name, + data[dim_name] = [] if empty else [element.level for _ in range(len(xs))] + mapping['fill_color'] = {'field': dim_name, 'transform': cmapper} if any(isinstance(t, HoverTool) for t in self.state.tools): diff --git a/tests/testplotinstantiation.py b/tests/testplotinstantiation.py index e55382e542..89ad2e2424 100644 --- a/tests/testplotinstantiation.py +++ b/tests/testplotinstantiation.py @@ -545,6 +545,18 @@ def test_polygons_colored_batched(self): source = plot.handles['source'] self.assertEqual(source.data['Value'], list(range(5))) + def test_polygons_colored_batched_unsanitized(self): + polygons = NdOverlay({j: Polygons([[(i**j, i) for i in range(10)] for i in range(2)], + level=j, vdims=['some ? unescaped name']) + for j in range(5)})(plot=dict(legend_limit=0)) + plot = list(bokeh_renderer.get_plot(polygons).subplots.values())[0] + cmapper = plot.handles['color_mapper'] + self.assertEqual(cmapper.low, 0) + self.assertEqual(cmapper.high, 4) + source = plot.handles['source'] + self.assertEqual(source.data['some_question_mark_unescaped_name'], + [j for i in range(5) for j in [i, i]]) + def test_points_colormapping(self): points = Points(np.random.rand(10, 4), vdims=['a', 'b'])(plot=dict(color_index=3)) self._test_colormapping(points, 3) From d6003349e1f5003e2e126842c91cfeb4df3db56d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 7 Apr 2017 17:23:18 +0100 Subject: [PATCH 4/4] Added comment about spike extents --- holoviews/plotting/bokeh/chart.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 922a687d0f..15459a3634 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -493,6 +493,8 @@ def get_extents(self, element, ranges): if len(element.dimensions()) == 1: if self.batched: bs, ts = [], [] + # Iterate over current NdOverlay and compute extents + # from position and length plot options for el in self.current_frame.values(): opts = self.lookup_options(el, 'plot').options pos = opts.get('position', self.position)