Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for Polygons plotting with holes #3409

Merged
merged 6 commits into from Jan 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion holoviews/core/data/__init__.py
Expand Up @@ -284,7 +284,7 @@ def range(self, dim, data_range=True, dimension_range=True):
return (None, None)
elif all(util.isfinite(v) for v in dim.range) and dimension_range:
return dim.range
elif dim in self.dimensions() and data_range and len(self):
elif dim in self.dimensions() and data_range and bool(self):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mainly an optimization, computing the length on a Polygons/Path can be very expensive, bool(self) is equivalent but a much quicker way to check whether the element is empty.

lower, upper = self.interface.range(self, dim)
else:
lower, upper = (np.NaN, np.NaN)
Expand Down
2 changes: 2 additions & 0 deletions holoviews/core/data/multipath.py
Expand Up @@ -129,6 +129,8 @@ def has_holes(cls, dataset):
@classmethod
def holes(cls, dataset):
holes = []
if not dataset.data:
return holes
ds = cls._inner_dataset_template(dataset)
for d in dataset.data:
ds.data = d
Expand Down
18 changes: 14 additions & 4 deletions holoviews/plotting/bokeh/path.py
Expand Up @@ -8,6 +8,7 @@
from ...core import util
from ...element import Polygons
from ...util.transform import dim
from .callbacks import PolyDrawCallback, PolyEditCallback
from .element import ColorbarPlot, LegendPlot
from .styles import (expand_batched_style, line_properties, fill_properties,
mpl_to_bokeh, validate)
Expand Down Expand Up @@ -151,6 +152,10 @@ class ContourPlot(LegendPlot, PathPlot):

_color_style = 'line_color'

def __init__(self, *args, **params):
super(ContourPlot, self).__init__(*args, **params)
self._has_holes = None

def _hover_opts(self, element):
if self.batched:
dims = list(self.hmap.last.kdims)+self.hmap.last.last.vdims
Expand Down Expand Up @@ -185,14 +190,20 @@ def _get_hover_data(self, data, element):
data[dim] = [v for _ in range(len(list(data.values())[0]))]

def get_data(self, element, ranges, style):
has_holes = isinstance(element, Polygons) and element.has_holes
if self._has_holes is None:
draw_callbacks = any(isinstance(cb, (PolyDrawCallback, PolyEditCallback))
for cb in self.callbacks)
has_holes = (isinstance(element, Polygons) and not draw_callbacks)
self._has_holes = has_holes
else:
has_holes = self._has_holes

if self.static_source:
data = dict()
xs = self.handles['cds'].data['xs']
else:
if has_holes and bokeh_version >= '1.0':
xs, ys = multi_polygons_data(element)
style['has_holes'] = has_holes
else:
paths = element.split(datatype='array', dimensions=element.kdims)
xs, ys = ([path[:, idx] for path in paths] for idx in (0, 1))
Expand Down Expand Up @@ -236,11 +247,10 @@ def _init_glyph(self, plot, mapping, properties):
"""
Returns a Bokeh glyph object.
"""
has_holes = properties.pop('has_holes', False)
plot_method = properties.pop('plot_method', None)
properties = mpl_to_bokeh(properties)
data = dict(properties, **mapping)
if has_holes:
if self._has_holes:
plot_method = 'multi_polygons'
elif plot_method is None:
plot_method = self._plot_methods.get('single')
Expand Down
5 changes: 5 additions & 0 deletions holoviews/tests/element/testpaths.py
Expand Up @@ -126,6 +126,11 @@ def test_multi_poly_holes_match(self):
self.assertEqual(len(holes[0][0]), 2)
self.assertEqual(len(holes[0][1]), 0)

def test_multi_poly_empty_holes(self):
poly = Polygons([])
self.assertFalse(poly.interface.has_holes(poly))
self.assertEqual(poly.interface.holes(poly), [])

def test_multi_poly_no_holes_match(self):
self.assertFalse(self.multi_poly_no_hole.interface.has_holes(self.multi_poly_no_hole))
paths = self.multi_poly_no_hole.split(datatype='array')
Expand Down
4 changes: 2 additions & 2 deletions holoviews/tests/plotting/bokeh/testlinks.py
Expand Up @@ -72,8 +72,8 @@ def test_data_link_poly_table(self):
plot = bokeh_renderer.get_plot(layout)
cds = list(plot.state.select({'type': ColumnDataSource}))
self.assertEqual(len(cds), 1)
merged_data = {'xs': [arr1[:, 0], arr2[:, 0]],
'ys': [arr1[:, 1], arr2[:, 1]],
merged_data = {'xs': [[[arr1[:, 0]]], [[arr2[:, 0]]]],
'ys': [[[arr1[:, 1]]], [[arr2[:, 1]]]],
'A': np.array(['A', 'B']), 'B': np.array([1, 2])}
for k, v in cds[0].data.items():
self.assertEqual(v, merged_data[k])
Expand Down
36 changes: 36 additions & 0 deletions holoviews/tests/plotting/bokeh/testpathplot.py
Expand Up @@ -6,6 +6,7 @@
from holoviews.core.options import Cycle
from holoviews.element import Path, Polygons, Contours
from holoviews.plotting.bokeh.util import bokeh_version
from holoviews.streams import PolyDraw

from .testplot import TestBokehPlot, bokeh_renderer

Expand Down Expand Up @@ -330,6 +331,41 @@ def test_polygons_line_width_op(self):
self.assertEqual(glyph.line_width, {'field': 'line_width'})
self.assertEqual(cds.data['line_width'], np.array([7, 3]))

def test_polygons_holes_initialize(self):
if bokeh_version < '1.0':
raise SkipTest('Plotting Polygons with holes requires bokeh >= 1.0')
from bokeh.models import MultiPolygons
xs = [1, 2, 3, np.nan, 6, 7, 3]
ys = [2, 0, 7, np.nan, 7, 5, 2]
holes = [
[[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]],
[]
]
poly = HoloMap({0: Polygons([{'x': xs, 'y': ys, 'holes': holes}]),
1: Polygons([{'x': xs, 'y': ys}])})
plot = bokeh_renderer.get_plot(poly)
glyph = plot.handles['glyph']
self.assertTrue(plot._has_holes)
self.assertIsInstance(glyph, MultiPolygons)

def test_polygons_no_holes_with_draw_tool(self):
if bokeh_version < '1.0':
raise SkipTest('Plotting Polygons with holes requires bokeh >= 1.0')
from bokeh.models import Patches
xs = [1, 2, 3, np.nan, 6, 7, 3]
ys = [2, 0, 7, np.nan, 7, 5, 2]
holes = [
[[(1.5, 2), (2, 3), (1.6, 1.6)], [(2.1, 4.5), (2.5, 5), (2.3, 3.5)]],
[]
]
poly = HoloMap({0: Polygons([{'x': xs, 'y': ys, 'holes': holes}]),
1: Polygons([{'x': xs, 'y': ys}])})
PolyDraw(source=poly)
plot = bokeh_renderer.get_plot(poly)
glyph = plot.handles['glyph']
self.assertFalse(plot._has_holes)
self.assertIsInstance(glyph, Patches)



class TestContoursPlot(TestBokehPlot):
Expand Down