From cca30ccca088e10434121135d274b9c45d8ceace Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 10 Oct 2018 22:07:36 +0100 Subject: [PATCH] Allow using explicit colormapping on non-categorical data (#3071) --- holoviews/plotting/bokeh/element.py | 12 ++++++++++-- holoviews/plotting/bokeh/graphs.py | 2 +- holoviews/tests/plotting/bokeh/testelementplot.py | 13 +++++++++++++ holoviews/tests/plotting/bokeh/testgraphplot.py | 12 ++++++------ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 8bbb21fef1..d595720695 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -1103,8 +1103,11 @@ def _get_colormapper(self, dim, element, ranges, style, factors=None, colors=Non cmap = colors or style.pop('cmap', 'viridis') nan_colors = {k: rgba_tuple(v) for k, v in self.clipping_colors.items()} - if isinstance(cmap, dict) and factors: + if isinstance(cmap, dict): + if not factors: + factors = list(cmap) palette = [cmap.get(f, nan_colors.get('NaN', self._default_nan)) for f in factors] + factors = [dim.pprint_value(f) for f in factors] else: categorical = ncolors is not None if isinstance(self.color_levels, int): @@ -1149,12 +1152,17 @@ def _get_color_data(self, element, ranges, style, name='color', factors=None, co if factors is None and (isinstance(cdata, list) or cdata.dtype.kind in dtypes): factors = list(util.unique_array(cdata)) if factors and int_categories and cdata.dtype.kind == 'i': - field += '_str' + field += '_str__' cdata = [str(f) for f in cdata] factors = [str(f) for f in factors] mapper = self._get_colormapper(cdim, element, ranges, style, factors, colors) + if not factors and isinstance(mapper, CategoricalColorMapper): + field += '_str__' + cdata = [cdim.pprint_value(c) for c in cdata] + factors = mapper.factors + data[field] = cdata if factors is not None and self.show_legend: mapping['legend'] = {'field': field} diff --git a/holoviews/plotting/bokeh/graphs.py b/holoviews/plotting/bokeh/graphs.py index cb88580b5c..d0eef34418 100644 --- a/holoviews/plotting/bokeh/graphs.py +++ b/holoviews/plotting/bokeh/graphs.py @@ -114,7 +114,7 @@ def _get_edge_colors(self, element, ranges, edge_data, edge_mapping, style): cvals = cvals.astype(np.int32) factors = factors.astype(np.int32) if factors.dtype.kind not in 'SU': - field += '_str' + field += '_str__' cvals = [str(f) for f in cvals] factors = (str(f) for f in factors) factors = list(factors) diff --git a/holoviews/tests/plotting/bokeh/testelementplot.py b/holoviews/tests/plotting/bokeh/testelementplot.py index 794f60f33f..9af5374f62 100644 --- a/holoviews/tests/plotting/bokeh/testelementplot.py +++ b/holoviews/tests/plotting/bokeh/testelementplot.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from nose.plugins.attrib import attr import numpy as np @@ -188,6 +189,18 @@ def test_colormapper_min_max_colors(self): self.assertEqual(cmapper.low_color, 'red') self.assertEqual(cmapper.high_color, 'blue') + def test_explicit_categorical_cmap_on_integer_data(self): + explicit_mapping = OrderedDict([(0, 'blue'), (1, 'red'), (2, 'green'), (3, 'purple')]) + points = Scatter(([0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]), vdims=['y', 'Category']).options( + color_index='Category', cmap=explicit_mapping + ) + plot = bokeh_renderer.get_plot(points) + cmapper = plot.handles['color_mapper'] + cds = plot.handles['cds'] + self.assertEqual(cds.data['Category_str__'], ['0', '1', '2', '3']) + self.assertEqual(cmapper.factors, ['0', '1', '2', '3']) + self.assertEqual(cmapper.palette, ['blue', 'red', 'green', 'purple']) + class TestOverlayPlot(TestBokehPlot): diff --git a/holoviews/tests/plotting/bokeh/testgraphplot.py b/holoviews/tests/plotting/bokeh/testgraphplot.py index bcf672b414..f316119a3b 100644 --- a/holoviews/tests/plotting/bokeh/testgraphplot.py +++ b/holoviews/tests/plotting/bokeh/testgraphplot.py @@ -142,8 +142,8 @@ def test_graph_edges_categorical_colormapped(self): self.assertIsInstance(cmapper, CategoricalColorMapper) factors = ['0', '1', '2', '3', '4', '5', '6', '7'] self.assertEqual(cmapper.factors, factors) - self.assertEqual(edge_source.data['start_str'], factors) - self.assertEqual(glyph.line_color, {'field': 'start_str', 'transform': cmapper}) + self.assertEqual(edge_source.data['start_str__'], factors) + self.assertEqual(glyph.line_color, {'field': 'start_str__', 'transform': cmapper}) def test_graph_edges_numerically_colormapped(self): g = self.graph4.opts(plot=dict(edge_color_index='Weight'), @@ -202,8 +202,8 @@ def test_graph_edges_categorical_colormapped(self): self.assertIsInstance(cmapper, CategoricalColorMapper) factors = ['0', '1', '2', '3'] self.assertEqual(cmapper.factors, factors) - self.assertEqual(edge_source.data['node1_str'], ['0', '1']) - self.assertEqual(glyph.line_color, {'field': 'node1_str', 'transform': cmapper}) + self.assertEqual(edge_source.data['node1_str__'], ['0', '1']) + self.assertEqual(glyph.line_color, {'field': 'node1_str__', 'transform': cmapper}) def test_graph_nodes_numerically_colormapped(self): g = self.trimesh_weighted.opts(plot=dict(edge_color_index='weight'), @@ -258,6 +258,6 @@ def test_chord_edges_categorically_colormapped(self): self.assertIsInstance(cmapper, CategoricalColorMapper) self.assertEqual(cmapper.palette, ['#FFFFFF', '#000000', '#FFFFFF']) self.assertEqual(cmapper.factors, ['0', '1', '2']) - self.assertEqual(edge_source.data['start_str'], ['0', '0', '1']) - self.assertEqual(glyph.line_color, {'field': 'start_str', 'transform': cmapper}) + self.assertEqual(edge_source.data['start_str__'], ['0', '0', '1']) + self.assertEqual(glyph.line_color, {'field': 'start_str__', 'transform': cmapper})