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)