From 72ca81b31e7259d315f4a6b0af6d6eeb82e7ac28 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 00:33:37 +0100
Subject: [PATCH 01/21] Implemented ColorbarPlot and LegendPlot baseclasses in
bokeh
---
holoviews/plotting/bokeh/element.py | 141 +++++++++++++++++++++++-----
1 file changed, 120 insertions(+), 21 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 1493fc7997..4e65648ca9 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -7,6 +7,14 @@
from bokeh.models.tickers import Ticker, BasicTicker, FixedTicker
from bokeh.models.widgets import Panel, Tabs
+from bokeh.models.mappers import LinearColorMapper
+try:
+ from bokeh.models import ColorBar
+ from bokeh.models.mappers import LogColorMapper
+except ImportError:
+ LogColorMapper, ColorBar = None, None
+from bokeh.models import LogTicker, BasicTicker
+
try:
from bokeh import mpl
except ImportError:
@@ -22,7 +30,8 @@
from ..util import dynamic_update
from .callbacks import Callbacks
from .plot import BokehPlot
-from .util import mpl_to_bokeh, convert_datetime, update_plot, bokeh_version
+from .util import (mpl_to_bokeh, convert_datetime, update_plot,
+ bokeh_version, mplcmap_to_palette)
if bokeh_version >= '0.12':
from bokeh.models import FuncTickFormatter
@@ -618,6 +627,115 @@ def framewise(self):
for frame in current_frames)
+
+class ColorbarPlot(ElementPlot):
+
+ colorbar = param.Boolean(default=False, doc="""
+ Whether to display a colorbar.""")
+
+ colorbar_position = param.ObjectSelector(objects=["top_right",
+ "top_left",
+ "bottom_left",
+ "bottom_right",
+ 'right', 'left',
+ 'top', 'bottom'],
+ default="right",
+ doc="""
+ Allows selecting between a number of predefined colorbar position
+ options. The predefined options may be customized in the
+ colorbar_specs class attribute.""")
+
+ colorbar_opts = param.Dict(default={}, doc="""
+ Allows setting specific styling options for the colorbar overriding
+ the options defined in the colorbar_specs class attribute. Includes
+ location, orientation, height, width, scale_alpha, title, title_props,
+ margin, padding, background_fill_color and more.""")
+
+ colorbar_specs = {'right': {'pos': 'right', 'opts': {'location': (0, 0)}},
+ 'left': {'pos': 'left', 'opts':{'location':(0, 0)}},
+ 'top_right': {'pos': 'center', 'opts': {'location': 'top_right'}},
+ 'top_left': {'pos': 'center', 'opts': {'location': 'top_left'}},
+ 'top': {'opts': {'location':(0, 0), 'orientation':'horizontal'},
+ 'pos': 'above'},
+ 'bottom': {'opts': {'location': (0, 0), 'orientation':'horizontal'},
+ 'pos': 'below'},
+ 'bottom_left': {'pos': 'center',
+ 'opts': {'location': 'bottom_center',
+ 'orientation': 'horizontal'}},
+ 'bottom_right': {'pos': 'center',
+ 'opts': {'location': 'bottom_right',
+ 'orientation': 'horizontal'}}}
+
+ logz = param.Boolean(default=False, doc="""
+ Whether to apply log scaling to the z-axis.""")
+
+ def _draw_colorbar(self, plot, color_mapper):
+ if LogColorMapper and isinstance(color_mapper, LogColorMapper):
+ ticker = LogTicker()
+ else:
+ ticker = BasicTicker()
+ cbar_opts = self.colorbar_specs[self.colorbar_position]
+
+ # Check if there is a colorbar in the same position
+ pos = cbar_opts['pos']
+ if any(isinstance(model, ColorBar) for model in getattr(plot, pos, [])):
+ return
+
+ color_bar = ColorBar(color_mapper=color_mapper, ticker=ticker,
+ **dict(cbar_opts['opts'], **self.colorbar_opts))
+
+ plot.add_layout(color_bar, pos)
+ self.handles['colorbar'] = color_bar
+
+
+ def _get_colormapper(self, dim, element, ranges, style):
+ low, high = ranges.get(dim.name)
+ if 'cmap' in style:
+ palette = mplcmap_to_palette(style.pop('cmap', None))
+ colormapper = LogColorMapper if self.logz else LinearColorMapper
+ cmapper = colormapper(palette, low=low, high=high)
+ if 'color_mapper' not in self.handles:
+ self.handles['color_mapper'] = cmapper
+ return cmapper
+
+
+ def _init_glyph(self, plot, mapping, properties):
+ """
+ Returns a Bokeh glyph object and optionally creates a colorbar.
+ """
+ ret = super(ColorbarPlot, self)._init_glyph(plot, mapping, properties)
+ if self.colorbar and 'color_mapper' in self.handles:
+ self._draw_colorbar(plot, self.handles['color_mapper'])
+ return ret
+
+
+
+class LegendPlot(ElementPlot):
+
+ legend_position = param.ObjectSelector(objects=["top_right",
+ "top_left",
+ "bottom_left",
+ "bottom_right",
+ 'right', 'left',
+ 'top', 'bottom'],
+ default="top_right",
+ doc="""
+ Allows selecting between a number of predefined legend position
+ options. The predefined options may be customized in the
+ legend_specs class attribute.""")
+
+
+ legend_cols = param.Integer(default=False, doc="""
+ Whether to lay out the legend as columns.""")
+
+
+ legend_specs = {'right': dict(pos='right', loc=(5, -40)),
+ 'left': dict(pos='left', loc=(0, -40)),
+ 'top': dict(pos='above', loc=(120, 5)),
+ 'bottom': dict(pos='below', loc=(60, 0))}
+
+
+
class BokehMPLWrapper(ElementPlot):
"""
Wraps an existing HoloViews matplotlib plot and converts
@@ -710,22 +828,8 @@ def update_frame(self, key, ranges=None, element=None):
self.handles['plot'] = self._render_plot(element)
-class OverlayPlot(GenericOverlayPlot, ElementPlot):
+class OverlayPlot(GenericOverlayPlot, LegendPlot):
- legend_position = param.ObjectSelector(objects=["top_right",
- "top_left",
- "bottom_left",
- "bottom_right",
- 'right', 'left',
- 'top', 'bottom'],
- default="top_right",
- doc="""
- Allows selecting between a number of predefined legend position
- options. The predefined options may be customized in the
- legend_specs class attribute.""")
-
- legend_cols = param.Integer(default=False, doc="""
- Whether to lay out the legend as columns.""")
tabs = param.Boolean(default=False, doc="""
Whether to display overlaid plots in separate panes""")
@@ -734,11 +838,6 @@ class OverlayPlot(GenericOverlayPlot, ElementPlot):
_update_handles = ['source']
- legend_specs = {'right': dict(pos='right', loc=(5, -40)),
- 'left': dict(pos='left', loc=(0, -40)),
- 'top': dict(pos='above', loc=(120, 5)),
- 'bottom': dict(pos='below', loc=(60, 0))}
-
def _process_legend(self):
plot = self.handles['plot']
if not self.show_legend or len(plot.legend) == 0:
From 14048bdbf1967eea1ee71dad1c4072266ef660d3 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 00:34:31 +0100
Subject: [PATCH 02/21] Added client side colormapping for PointPlot
---
holoviews/plotting/bokeh/chart.py | 27 ++++++++++-----------------
1 file changed, 10 insertions(+), 17 deletions(-)
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index 5a857f7940..8e3432b18f 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -12,12 +12,12 @@
from ...core.util import max_range, basestring, dimension_sanitizer
from ...core.options import abbreviated_exception
from ..util import compute_sizes, get_sideplot_ranges, match_spec, map_colors
-from .element import ElementPlot, line_properties, fill_properties
+from .element import ElementPlot, ColorbarPlot, line_properties, fill_properties
from .path import PathPlot, PolygonPlot
from .util import get_cmap, mpl_to_bokeh, update_plot, rgb2hex, bokeh_version
-class PointPlot(ElementPlot):
+class PointPlot(ColorbarPlot):
color_index = param.ClassSelector(default=3, class_=(basestring, int),
allow_None=True, doc="""
@@ -55,21 +55,12 @@ def get_data(self, element, ranges=None, empty=False):
mapping = dict(x=dims[xidx], y=dims[yidx])
data = {}
- cmap = style.get('palette', style.get('cmap', None))
cdim = element.get_dimension(self.color_index)
- if cdim and cmap:
- map_key = 'color_' + cdim.name
- mapping['color'] = map_key
- if empty:
- data[map_key] = []
- else:
- cmap = get_cmap(cmap)
- colors = element.dimension_values(self.color_index)
- if colors.dtype.kind in 'if':
- crange = ranges.get(cdim.name, element.range(cdim.name))
- else:
- crange = np.unique(colors)
- data[map_key] = map_colors(colors, crange, cmap)
+ if cdim:
+ mapper = self._get_colormapper(cdim, element, ranges, style)
+ data[cdim.name] = [] if empty else element.dimension_values(cdim)
+ mapping['color'] = {'field': cdim.name,
+ 'transform': mapper}
sdim = element.get_dimension(self.size_index)
if sdim:
@@ -98,7 +89,7 @@ def get_batched_data(self, element, ranges=None, empty=False):
eldata, elmapping = self.get_data(el, ranges, empty)
for k, eld in eldata.items():
data[k].append(eld)
- if 'color' not in eldata:
+ if 'color' not in elmapping:
zorder = self.get_zorder(element, key, el)
val = style[zorder].get('color')
elmapping['color'] = 'color'
@@ -128,6 +119,8 @@ def _init_glyph(self, plot, mapping, properties):
else:
plot_method = self._plot_methods.get('batched' if self.batched else 'single')
renderer = getattr(plot, plot_method)(**dict(properties, **mapping))
+ if self.colorbar and 'color_mapper' in self.handles:
+ self._draw_colorbar(plot, self.handles['color_mapper'])
return renderer, renderer.glyph
From 3e383f8e2f6d47794100d36141e9b939943ab14c Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 00:35:21 +0100
Subject: [PATCH 03/21] Added client side colormapping for SideHistogramPlot
---
holoviews/plotting/bokeh/chart.py | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index 8e3432b18f..36472c817c 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -232,7 +232,7 @@ def get_data(self, element, ranges=None, empty=None):
return (data, mapping)
-class SideHistogramPlot(HistogramPlot):
+class SideHistogramPlot(HistogramPlot, ColorbarPlot):
style_opts = HistogramPlot.style_opts + ['cmap']
@@ -255,19 +255,20 @@ def get_data(self, element, ranges=None, empty=None):
data = dict(top=element.values, left=element.edges[:-1],
right=element.edges[1:])
- dim = element.get_dimension(0).name
+ dim = element.get_dimension(0)
main = self.adjoined.main
- range_item, main_range, dim = get_sideplot_ranges(self, element, main, ranges)
- vals = element.dimension_values(dim)
+ range_item, main_range, _ = get_sideplot_ranges(self, element, main, ranges)
if isinstance(range_item, (Raster, Points, Polygons, Spikes)):
style = self.lookup_options(range_item, 'style')[self.cyclic_index]
else:
style = {}
if 'cmap' in style or 'palette' in style:
- cmap = get_cmap(style.get('cmap', style.get('palette', None)))
- data['color'] = [] if empty else map_colors(vals, main_range, cmap)
- mapping['fill_color'] = 'color'
+ main_range = {dim.name: main_range}
+ mapper = self._get_colormapper(dim, element, main_range, style)
+ data[dim.name] = [] if empty else element.dimension_values(dim)
+ mapping['fill_color'] = {'field': dim.name,
+ 'transform': mapper}
self._get_hover_data(data, element, empty)
return (data, mapping)
From 883e185dc5da1c1cca21d374862a0d3afd5f7e27 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 00:36:11 +0100
Subject: [PATCH 04/21] Added client side colormapping for PolygonPlot
---
holoviews/plotting/bokeh/path.py | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/holoviews/plotting/bokeh/path.py b/holoviews/plotting/bokeh/path.py
index eedc5566fa..e4cc8118cf 100644
--- a/holoviews/plotting/bokeh/path.py
+++ b/holoviews/plotting/bokeh/path.py
@@ -7,7 +7,7 @@
from ...core import util
from ..util import map_colors
-from .element import ElementPlot, line_properties, fill_properties
+from .element import ElementPlot, ColorbarPlot, line_properties, fill_properties
from .util import get_cmap, rgb2hex
@@ -44,7 +44,7 @@ def get_batched_data(self, element, ranges=None, empty=False):
return data, elmapping
-class PolygonPlot(PathPlot):
+class PolygonPlot(ColorbarPlot, PathPlot):
style_opts = ['color', 'cmap', 'palette'] + line_properties + fill_properties
_plot_methods = dict(single='patches', batched='patches')
@@ -74,15 +74,17 @@ def get_data(self, element, ranges=None, empty=False):
data = dict(xs=ys, ys=xs) if self.invert_axes else dict(xs=xs, ys=ys)
style = self.style[self.cyclic_index]
- cmap = style.get('palette', style.get('cmap', None))
mapping = dict(self._mapping)
- if cmap and element.level is not None:
- cmap = get_cmap(cmap)
- colors = map_colors(np.array([element.level]), ranges[element.vdims[0].name], cmap)
- mapping['color'] = 'color'
- data['color'] = [] if empty else list(colors)*len(element.data)
- dim_name = util.dimension_sanitizer(element.vdims[0].name)
+
+ if element.vdims and element.level is not None:
+ cdim = element.vdims[0]
+ mapper = self._get_colormapper(cdim, element, ranges, style)
+ data[cdim.name] = [] if empty else element.dimension_values(2)
+ mapping['fill_color'] = {'field': cdim.name,
+ 'transform': mapper}
+
if 'hover' in self.tools+self.default_tools:
+ dim_name = util.dimension_sanitizer(element.vdims[0].name)
for k, v in self.overlay_dims.items():
dim = util.dimension_sanitizer(k.name)
data[dim] = [v for _ in range(len(xs))]
@@ -99,7 +101,7 @@ def get_batched_data(self, element, ranges=None, empty=False):
eldata, elmapping = self.get_data(el, ranges, empty)
for k, eld in eldata.items():
data[k].extend(eld)
- if 'color' not in eldata:
+ if 'color' not in elmapping:
zorder = self.get_zorder(element, key, el)
val = style[zorder].get('color')
elmapping['color'] = 'color'
From ac35b2f4330a9ce5e1d5abdbd621e4010f0b069a Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 00:37:06 +0100
Subject: [PATCH 05/21] Added colorbars for RasterPlot
---
holoviews/plotting/bokeh/raster.py | 27 ++++++---------------------
1 file changed, 6 insertions(+), 21 deletions(-)
diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py
index 3d1819093a..0ddde4f4d2 100644
--- a/holoviews/plotting/bokeh/raster.py
+++ b/holoviews/plotting/bokeh/raster.py
@@ -1,24 +1,15 @@
import numpy as np
import param
-from bokeh.models.mappers import LinearColorMapper
-try:
- from bokeh.models.mappers import LogColorMapper
-except ImportError:
- LogColorMapper = None
-
from ...core.util import cartesian_product
from ...element import Image, Raster, RGB
from ..renderer import SkipRendering
from ..util import map_colors
-from .element import ElementPlot, line_properties, fill_properties
-from .util import mplcmap_to_palette, get_cmap, hsv_to_rgb
-
+from .element import ElementPlot, ColorbarPlot, line_properties, fill_properties
+from .util import mplcmap_to_palette, get_cmap, hsv_to_rgb, mpl_to_bokeh
-class RasterPlot(ElementPlot):
- logz = param.Boolean(default=False, doc="""
- Whether to apply log scaling to the z-axis.""")
+class RasterPlot(ColorbarPlot):
show_legend = param.Boolean(default=False, doc="""
Whether to show legend for the plot.""")
@@ -56,15 +47,9 @@ def _glyph_properties(self, plot, element, source, ranges):
properties = super(RasterPlot, self)._glyph_properties(plot, element,
source, ranges)
properties = {k: v for k, v in properties.items()}
- val_dim = [d.name for d in element.vdims][0]
- low, high = ranges.get(val_dim)
- if 'cmap' in properties:
- palette = mplcmap_to_palette(properties.pop('cmap', None))
- colormapper = LogColorMapper if self.logz else LinearColorMapper
- cmap = colormapper(palette, low=low, high=high)
- properties['color_mapper'] = cmap
- if 'color_mapper' not in self.handles:
- self.handles['color_mapper'] = cmap
+ val_dim = [d for d in element.vdims][0]
+ properties['color_mapper'] = self._get_colormapper(val_dim, element, ranges,
+ properties)
return properties
From da511ff9fa57ac640ce1861cd8324d9c49bbbfed Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 00:37:33 +0100
Subject: [PATCH 06/21] Added client-side colormapping for QuadMeshPlot
---
holoviews/plotting/bokeh/raster.py | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py
index 0ddde4f4d2..8111a5ba4c 100644
--- a/holoviews/plotting/bokeh/raster.py
+++ b/holoviews/plotting/bokeh/raster.py
@@ -151,7 +151,7 @@ def get_data(self, element, ranges=None, empty=False):
return (data, {'x': x, 'y': y, 'fill_color': 'color', 'height': 1, 'width': 1})
-class QuadMeshPlot(ElementPlot):
+class QuadMeshPlot(ColorbarPlot):
show_legend = param.Boolean(default=False, doc="""
Whether to show legend for the plot.""")
@@ -161,24 +161,23 @@ class QuadMeshPlot(ElementPlot):
def get_data(self, element, ranges=None, empty=False):
x, y, z = element.dimensions(label=True)
+ style = self.style[self.cyclic_index]
+ cmapper = self._get_colormapper(element.vdims[0], element, ranges, style)
if empty:
- data = {x: [], y: [], z: [], 'color': [], 'height': [], 'width': []}
+ data = {x: [], y: [], z: [], 'height': [], 'width': []}
else:
- style = self.style[self.cyclic_index]
- cmap = style.get('palette', style.get('cmap', None))
- cmap = get_cmap(cmap)
if len(set(v.shape for v in element.data)) == 1:
raise SkipRendering("Bokeh QuadMeshPlot only supports rectangular meshes")
zvals = element.data[2].T.flatten()
- colors = map_colors(zvals, ranges[z], cmap)
xvals = element.dimension_values(0, False)
yvals = element.dimension_values(1, False)
widths = np.diff(element.data[0])
heights = np.diff(element.data[1])
xs, ys = cartesian_product([xvals, yvals])
ws, hs = cartesian_product([widths, heights])
- data = {x: xs.flat, y: ys.flat, z: zvals, 'color': colors,
+ data = {x: xs.flat, y: ys.flat, z: zvals,
'widths': ws.flat, 'heights': hs.flat}
- return (data, {'x': x, 'y': y, 'fill_color': 'color',
+ return (data, {'x': x, 'y': y,
+ 'fill_color': {'field': z, 'transform': cmapper},
'height': 'heights', 'width': 'widths'})
From 59b94736bcbc085b7839fa7ffd96e22aeae999b4 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 00:38:24 +0100
Subject: [PATCH 07/21] Added client side colormapping for SpikesPlot
---
holoviews/plotting/bokeh/chart.py | 22 +++++++---------------
1 file changed, 7 insertions(+), 15 deletions(-)
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index 36472c817c..a7d48ee571 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -308,7 +308,7 @@ def get_data(self, element, ranges=None, empty=False):
return (data, dict(self._mapping))
-class SpikesPlot(PathPlot):
+class SpikesPlot(PathPlot, ColorbarPlot):
color_index = param.ClassSelector(default=1, class_=(basestring, int), doc="""
Index of the dimension from which the color will the drawn""")
@@ -346,22 +346,14 @@ def get_data(self, element, ranges=None, empty=False):
xs, ys = zip(*(((x[0], x[0]), (pos+height, pos))
for x in element.array(dims[:1])))
- if not empty and self.invert_axes: keys = keys[::-1]
+ if not empty and self.invert_axes: xs, ys = ys, xs
data = dict(zip(('xs', 'ys'), (xs, ys)))
-
- cmap = style.get('palette', style.get('cmap', None))
cdim = element.get_dimension(self.color_index)
- if cdim and cmap:
- map_key = 'color_' + cdim.name
- mapping['color'] = map_key
- if empty:
- colors = []
- else:
- cmap = get_cmap(cmap)
- cvals = element.dimension_values(cdim)
- crange = ranges.get(cdim.name, None)
- colors = map_colors(cvals, crange, cmap)
- data[map_key] = colors
+ if cdim:
+ mapper = self._get_colormapper(cdim, element, ranges, style)
+ data[cdim.name] = [] if empty else element.dimension_values(cdim)
+ mapping['color'] = {'field': cdim.name,
+ 'transform': mapper}
if 'hover' in self.tools+self.default_tools and not empty:
for d in dims:
From 84a0dc44458078c42a9b79224c9e0fe6cb3928be Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 00:38:51 +0100
Subject: [PATCH 08/21] Moved toolbar on top to avoid clashes with colorbar
---
holoviews/plotting/bokeh/element.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 4e65648ca9..6239e68a72 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -289,6 +289,7 @@ def _init_plot(self, key, element, plots, ranges=None):
properties['webgl'] = Store.renderers[self.renderer.backend].webgl
return bokeh.plotting.Figure(x_axis_type=x_axis_type,
+ toolbar_location='above',
y_axis_type=y_axis_type, title=title,
tools=tools, **properties)
From dee556fb4f8a0d732bf05e97267f6cc43db6dea6 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 17:37:27 +0100
Subject: [PATCH 09/21] Added client side colormapping for HeatmapPlot
---
holoviews/plotting/bokeh/raster.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py
index 8111a5ba4c..961c244460 100644
--- a/holoviews/plotting/bokeh/raster.py
+++ b/holoviews/plotting/bokeh/raster.py
@@ -116,7 +116,7 @@ def get_data(self, element, ranges=None, empty=False):
return super(HSVPlot, self).get_data(rgb, ranges, empty)
-class HeatmapPlot(ElementPlot):
+class HeatmapPlot(ColorbarPlot):
show_legend = param.Boolean(default=False, doc="""
Whether to show legend for the plot.""")
@@ -129,6 +129,7 @@ def _axes_props(self, plots, subplots, element, ranges):
labels = self._get_axis_labels(dims)
xvals, yvals = [element.dimension_values(i, False)
for i in range(2)]
+ if self.invert_yaxis: yvals = yvals[::-1]
plot_ranges = {'x_range': [str(x) for x in xvals],
'y_range': [str(y) for y in yvals]}
return ('auto', 'auto'), labels, plot_ranges
@@ -136,19 +137,18 @@ def _axes_props(self, plots, subplots, element, ranges):
def get_data(self, element, ranges=None, empty=False):
x, y, z = element.dimensions(label=True)
+ style = self.style[self.cyclic_index]
+ cmapper = self._get_colormapper(element.vdims[0], element, ranges, style)
if empty:
data = {x: [], y: [], z: [], 'color': []}
else:
- style = self.style[self.cyclic_index]
- cmap = style.get('palette', style.get('cmap', None))
- cmap = get_cmap(cmap)
zvals = np.rot90(element.raster, 3).flatten()
- colors = map_colors(zvals, ranges[z], cmap)
xvals, yvals = [[str(v) for v in element.dimension_values(i)]
for i in range(2)]
- data = {x: xvals, y: yvals, z: zvals, 'color': colors}
+ data = {x: xvals, y: yvals, z: zvals}
- return (data, {'x': x, 'y': y, 'fill_color': 'color', 'height': 1, 'width': 1})
+ return (data, {'x': x, 'y': y, 'fill_color': {'field': z, 'transform': cmapper},
+ 'height': 1, 'width': 1})
class QuadMeshPlot(ColorbarPlot):
From f2884e1f103c609370954d437ac6c3f90c36d6e7 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 18:02:34 +0100
Subject: [PATCH 10/21] Handled updating of colormapping ranges
---
holoviews/plotting/bokeh/element.py | 13 +++++++++++++
holoviews/plotting/bokeh/raster.py | 10 ----------
2 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 6239e68a72..cf8cd116ff 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -710,6 +710,19 @@ def _init_glyph(self, plot, mapping, properties):
return ret
+ def _update_glyph(self, glyph, properties, mapping):
+ allowed_properties = glyph.properties()
+ cmappers = [v.get('transform') for v in mapping.values()
+ if isinstance(v, dict)]
+ cmappers.append(properties.pop('color_mapper', None))
+ for cm in cmappers:
+ if cm:
+ self.handles['color_mapper'].low = cm.low
+ self.handles['color_mapper'].high = cm.high
+ merged = dict(properties, **mapping)
+ glyph.set(**{k: v for k, v in merged.items()
+ if k in allowed_properties})
+
class LegendPlot(ElementPlot):
diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py
index 961c244460..f70e952378 100644
--- a/holoviews/plotting/bokeh/raster.py
+++ b/holoviews/plotting/bokeh/raster.py
@@ -53,16 +53,6 @@ def _glyph_properties(self, plot, element, source, ranges):
return properties
- def _update_glyph(self, glyph, properties, mapping):
- allowed_properties = glyph.properties()
- cmap = properties.pop('color_mapper', None)
- if cmap:
- glyph.color_mapper.low = cmap.low
- glyph.color_mapper.high = cmap.high
- merged = dict(properties, **mapping)
- glyph.set(**{k: v for k, v in merged.items()
- if k in allowed_properties})
-
class ImagePlot(RasterPlot):
From 8f00b04cdce69c37660dbca25c680a4993c14670 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 18:09:42 +0100
Subject: [PATCH 11/21] Added colorbar border by default
---
holoviews/plotting/bokeh/element.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index cf8cd116ff..e1d2c4ba0c 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -675,7 +675,9 @@ def _draw_colorbar(self, plot, color_mapper):
ticker = LogTicker()
else:
ticker = BasicTicker()
- cbar_opts = self.colorbar_specs[self.colorbar_position]
+ cbar_opts = dict(self.colorbar_specs[self.colorbar_position],
+ bar_line_color='black', label_standoff=8,
+ major_tick_line_color='black')
# Check if there is a colorbar in the same position
pos = cbar_opts['pos']
From af0b2dd0af6174d6b47ed6ab2e70e7c2587534e1 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 18:52:32 +0100
Subject: [PATCH 12/21] Made toolbar position customizable
Allows avoiding overlap with legends, colorbars and titles
---
holoviews/plotting/bokeh/element.py | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index e1d2c4ba0c..e924b1dadf 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -113,6 +113,13 @@ class ElementPlot(BokehPlot, GenericElementPlot):
tools = param.List(default=[], doc="""
A list of plugin tools to use on the plot.""")
+ toolbar = param.ObjectSelector(default='right',
+ objects=["above", "below",
+ "left", "right", None],
+ doc="""
+ The toolbar location, must be one of 'above', 'below',
+ 'left', 'right', None.""")
+
xaxis = param.ObjectSelector(default='bottom',
objects=['top', 'bottom', 'bare', 'top-bare',
'bottom-bare', None], doc="""
@@ -277,7 +284,6 @@ def _init_plot(self, key, element, plots, ranges=None):
axis_types, labels, plot_ranges = self._axes_props(plots, subplots, element, ranges)
xlabel, ylabel, _ = labels
x_axis_type, y_axis_type = axis_types
- tools = self._init_tools(element)
properties = dict(plot_ranges)
properties['x_axis_label'] = xlabel if 'x' in self.show_labels else ' '
properties['y_axis_label'] = ylabel if 'y' in self.show_labels else ' '
@@ -287,11 +293,15 @@ def _init_plot(self, key, element, plots, ranges=None):
else:
title = ''
+ if self.toolbar:
+ tools = self._init_tools(element)
+ properties['tools'] = tools
+ properties['toolbar_location'] = self.toolbar
+
properties['webgl'] = Store.renderers[self.renderer.backend].webgl
return bokeh.plotting.Figure(x_axis_type=x_axis_type,
- toolbar_location='above',
y_axis_type=y_axis_type, title=title,
- tools=tools, **properties)
+ **properties)
def _plot_properties(self, key, plot, element):
From 122a3bab22d7161ffb84c9fd6ac95e208f621533 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 19:04:56 +0100
Subject: [PATCH 13/21] Cleaned up ColorbarPlot and added docstring
---
holoviews/plotting/bokeh/element.py | 53 ++++++++++++++++-------------
1 file changed, 30 insertions(+), 23 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index e924b1dadf..53451291be 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -640,18 +640,40 @@ def framewise(self):
class ColorbarPlot(ElementPlot):
+ """
+ ColorbarPlot provides methods to create colormappers and colorbar
+ models which can be added to a glyph. Additionally it provides
+ parameters to control the position and other styling options of
+ the colorbar. The default colorbar_position options are defined
+ by the colorbar_specs, but may be overridden by the colorbar_opts.
+ """
+
+ colorbar_specs = {'right': {'pos': 'right',
+ 'opts': {'location': (0, 0)}},
+ 'left': {'pos': 'left',
+ 'opts':{'location':(0, 0)}},
+ 'bottom': {'pos': 'below',
+ 'opts': {'location': (0, 0),
+ 'orientation':'horizontal'}},
+ 'top': {'pos': 'above',
+ 'opts': {'location':(0, 0),
+ 'orientation':'horizontal'}},
+ 'top_right': {'pos': 'center',
+ 'opts': {'location': 'top_right'}},
+ 'top_left': {'pos': 'center',
+ 'opts': {'location': 'top_left'}},
+ 'bottom_left': {'pos': 'center',
+ 'opts': {'location': 'bottom_left',
+ 'orientation': 'horizontal'}},
+ 'bottom_right': {'pos': 'center',
+ 'opts': {'location': 'bottom_right',
+ 'orientation': 'horizontal'}}}
colorbar = param.Boolean(default=False, doc="""
Whether to display a colorbar.""")
- colorbar_position = param.ObjectSelector(objects=["top_right",
- "top_left",
- "bottom_left",
- "bottom_right",
- 'right', 'left',
- 'top', 'bottom'],
- default="right",
- doc="""
+ colorbar_position = param.ObjectSelector(objects=list(colorbar_specs),
+ default="right", doc="""
Allows selecting between a number of predefined colorbar position
options. The predefined options may be customized in the
colorbar_specs class attribute.""")
@@ -662,21 +684,6 @@ class ColorbarPlot(ElementPlot):
location, orientation, height, width, scale_alpha, title, title_props,
margin, padding, background_fill_color and more.""")
- colorbar_specs = {'right': {'pos': 'right', 'opts': {'location': (0, 0)}},
- 'left': {'pos': 'left', 'opts':{'location':(0, 0)}},
- 'top_right': {'pos': 'center', 'opts': {'location': 'top_right'}},
- 'top_left': {'pos': 'center', 'opts': {'location': 'top_left'}},
- 'top': {'opts': {'location':(0, 0), 'orientation':'horizontal'},
- 'pos': 'above'},
- 'bottom': {'opts': {'location': (0, 0), 'orientation':'horizontal'},
- 'pos': 'below'},
- 'bottom_left': {'pos': 'center',
- 'opts': {'location': 'bottom_center',
- 'orientation': 'horizontal'}},
- 'bottom_right': {'pos': 'center',
- 'opts': {'location': 'bottom_right',
- 'orientation': 'horizontal'}}}
-
logz = param.Boolean(default=False, doc="""
Whether to apply log scaling to the z-axis.""")
From b219c23b4f456b9d05d756f69801c4f1a7e6795e Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 22:04:36 +0100
Subject: [PATCH 14/21] Defined default colormap
---
holoviews/plotting/bokeh/element.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 53451291be..9f8f9b69ee 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -710,8 +710,7 @@ def _draw_colorbar(self, plot, color_mapper):
def _get_colormapper(self, dim, element, ranges, style):
low, high = ranges.get(dim.name)
- if 'cmap' in style:
- palette = mplcmap_to_palette(style.pop('cmap', None))
+ palette = mplcmap_to_palette(style.pop('cmap', 'viridis'))
colormapper = LogColorMapper if self.logz else LinearColorMapper
cmapper = colormapper(palette, low=low, high=high)
if 'color_mapper' not in self.handles:
From 8a83577b48f9ad442d03e69e29ae6bc2aaa1b357 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 22:05:55 +0100
Subject: [PATCH 15/21] Added colormapping unit tests
---
tests/testplotinstantiation.py | 49 ++++++++++++++++++++++++++++++++--
1 file changed, 47 insertions(+), 2 deletions(-)
diff --git a/tests/testplotinstantiation.py b/tests/testplotinstantiation.py
index 339a45eb37..f8c6399cb9 100644
--- a/tests/testplotinstantiation.py
+++ b/tests/testplotinstantiation.py
@@ -6,8 +6,9 @@
from io import BytesIO
import numpy as np
-from holoviews import (Dimension, Curve, Scatter, Overlay, DynamicMap,
- Store, Image, VLine, NdOverlay, Points)
+from holoviews import (Dimension, Overlay, DynamicMap, Store, NdOverlay)
+from holoviews.element import (Curve, Scatter, Image, VLine, Points,
+ HeatMap, QuadMesh, Spikes)
from holoviews.element.comparison import ComparisonTestCase
from holoviews.streams import PositionXY
@@ -24,6 +25,7 @@
try:
import holoviews.plotting.bokeh
bokeh_renderer = Store.renderers['bokeh']
+ from bokeh.models.mappers import LinearColorMapper, LogColorMapper
except:
bokeh_renderer = None
@@ -31,6 +33,8 @@
class TestMPLPlotInstantiation(ComparisonTestCase):
def setUp(self):
+ self.previous_backend = Store.current_backend
+ Store.current_backend = 'matplotlib'
if mpl_renderer is None:
raise SkipTest("Matplotlib required to test plot instantiation")
self.default_comm, _ = mpl_renderer.comms['default']
@@ -38,6 +42,7 @@ def setUp(self):
def teardown(self):
mpl_renderer.comms['default'] = (self.default_comm, '')
+ Store.current_backend = self.previous_backend
def test_interleaved_overlay(self):
"""
@@ -72,11 +77,51 @@ def test_dynamic_streams_refresh(self):
class TestBokehPlotInstantiation(ComparisonTestCase):
def setUp(self):
+ self.previous_backend = Store.current_backend
+ Store.current_backend = 'bokeh'
+
if not bokeh_renderer:
raise SkipTest("Bokeh required to test plot instantiation")
+ def teardown(self):
+ Store.current_backend = self.previous_backend
+
def test_batched_plot(self):
overlay = NdOverlay({i: Points(np.arange(i)) for i in range(1, 100)})
plot = bokeh_renderer.get_plot(overlay)
extents = plot.get_extents(overlay, {})
self.assertEqual(extents, (0, 0, 98, 98))
+
+ def _test_colormapping(self, element, dim, log=False):
+ plot = bokeh_renderer.get_plot(element)
+ plot.initialize_plot()
+ fig = plot.state
+ cmapper = plot.handles['color_mapper']
+ low, high = element.range(dim)
+ self.assertEqual(cmapper.low, low)
+ self.assertEqual(cmapper.high, high)
+ mapper_type = LogColorMapper if log else LinearColorMapper
+ self.assertTrue(isinstance(cmapper, mapper_type))
+
+ def test_points_colormapping(self):
+ points = Points(np.random.rand(10, 4), vdims=['a', 'b'])
+ self._test_colormapping(points, 3)
+
+ def test_image_colormapping(self):
+ img = Image(np.random.rand(10, 10))(plot=dict(logz=True))
+ self._test_colormapping(img, 2, True)
+
+ def test_heatmap_colormapping(self):
+ hm = HeatMap([(1,1,1), (2,2,0)])
+ self._test_colormapping(hm, 2)
+
+ def test_quadmesh_colormapping(self):
+ n = 21
+ xs = np.logspace(1, 3, n)
+ ys = np.linspace(1, 10, n)
+ qmesh = QuadMesh((xs, ys, np.random.rand(n-1, n-1)))
+ self._test_colormapping(qmesh, 2)
+
+ def test_spikes_colormapping(self):
+ spikes = Spikes(np.random.rand(20, 2), vdims=['Intensity'])
+ self._test_colormapping(spikes, 1)
From 65c0d21cfa948add89e0df5689ace8211998211a Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 22:20:46 +0100
Subject: [PATCH 16/21] Added colormapper to update handles of ColorbarPlot
---
holoviews/plotting/bokeh/element.py | 2 ++
holoviews/plotting/bokeh/raster.py | 1 -
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 9f8f9b69ee..fa317814be 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -687,6 +687,8 @@ class ColorbarPlot(ElementPlot):
logz = param.Boolean(default=False, doc="""
Whether to apply log scaling to the z-axis.""")
+ _update_handles = ['color_mapper', 'source', 'glyph']
+
def _draw_colorbar(self, plot, color_mapper):
if LogColorMapper and isinstance(color_mapper, LogColorMapper):
ticker = LogTicker()
diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py
index f70e952378..270cde329b 100644
--- a/holoviews/plotting/bokeh/raster.py
+++ b/holoviews/plotting/bokeh/raster.py
@@ -16,7 +16,6 @@ class RasterPlot(ColorbarPlot):
style_opts = ['cmap']
_plot_methods = dict(single='image')
- _update_handles = ['color_mapper', 'source', 'glyph']
def __init__(self, *args, **kwargs):
super(RasterPlot, self).__init__(*args, **kwargs)
From c211f2a8c222c726584931bc0bc971538a5b4271 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 22:42:31 +0100
Subject: [PATCH 17/21] Fixed setting of colorbar border
---
holoviews/plotting/bokeh/element.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index fa317814be..6fa80b516f 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -694,17 +694,17 @@ def _draw_colorbar(self, plot, color_mapper):
ticker = LogTicker()
else:
ticker = BasicTicker()
- cbar_opts = dict(self.colorbar_specs[self.colorbar_position],
- bar_line_color='black', label_standoff=8,
- major_tick_line_color='black')
+ cbar_opts = dict(self.colorbar_specs[self.colorbar_position])
# Check if there is a colorbar in the same position
pos = cbar_opts['pos']
if any(isinstance(model, ColorBar) for model in getattr(plot, pos, [])):
return
+ opts = dict(cbar_opts['opts'], bar_line_color='black',
+ label_standoff=8, major_tick_line_color='black')
color_bar = ColorBar(color_mapper=color_mapper, ticker=ticker,
- **dict(cbar_opts['opts'], **self.colorbar_opts))
+ **dict(opts, **self.colorbar_opts))
plot.add_layout(color_bar, pos)
self.handles['colorbar'] = color_bar
From f520bff3ca36df40b112391555cc832306883894 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 23:31:05 +0100
Subject: [PATCH 18/21] Allowed updating the colormapper palette
---
holoviews/plotting/bokeh/element.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 6fa80b516f..523fb9bfef 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -739,6 +739,7 @@ def _update_glyph(self, glyph, properties, mapping):
if cm:
self.handles['color_mapper'].low = cm.low
self.handles['color_mapper'].high = cm.high
+ self.handles['color_mapper'].palette = cm.palette
merged = dict(properties, **mapping)
glyph.set(**{k: v for k, v in merged.items()
if k in allowed_properties})
From c35d6b26c12b334828b77e083285da177264fcf4 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 23:34:13 +0100
Subject: [PATCH 19/21] Made colorbar defaults a class attribute
---
holoviews/plotting/bokeh/element.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 523fb9bfef..d567b93e52 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -689,6 +689,9 @@ class ColorbarPlot(ElementPlot):
_update_handles = ['color_mapper', 'source', 'glyph']
+ _colorbar_defaults = dict(bar_line_color='black', label_standoff=8,
+ major_tick_line_color='black')
+
def _draw_colorbar(self, plot, color_mapper):
if LogColorMapper and isinstance(color_mapper, LogColorMapper):
ticker = LogTicker()
@@ -701,8 +704,7 @@ def _draw_colorbar(self, plot, color_mapper):
if any(isinstance(model, ColorBar) for model in getattr(plot, pos, [])):
return
- opts = dict(cbar_opts['opts'], bar_line_color='black',
- label_standoff=8, major_tick_line_color='black')
+ opts = dict(cbar_opts['opts'], self._colorbar_defaults)
color_bar = ColorBar(color_mapper=color_mapper, ticker=ticker,
**dict(opts, **self.colorbar_opts))
From 80899e61a533c0f91f9a4e612354c2c2bc5d7c06 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 23:35:55 +0100
Subject: [PATCH 20/21] Renamed mapper to cmapper
---
holoviews/plotting/bokeh/chart.py | 8 ++++----
holoviews/plotting/bokeh/path.py | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index a7d48ee571..5446e3d8c3 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -265,10 +265,10 @@ def get_data(self, element, ranges=None, empty=None):
if 'cmap' in style or 'palette' in style:
main_range = {dim.name: main_range}
- mapper = self._get_colormapper(dim, element, main_range, style)
+ cmapper = self._get_colormapper(dim, element, main_range, style)
data[dim.name] = [] if empty else element.dimension_values(dim)
mapping['fill_color'] = {'field': dim.name,
- 'transform': mapper}
+ 'transform': cmapper}
self._get_hover_data(data, element, empty)
return (data, mapping)
@@ -350,10 +350,10 @@ def get_data(self, element, ranges=None, empty=False):
data = dict(zip(('xs', 'ys'), (xs, ys)))
cdim = element.get_dimension(self.color_index)
if cdim:
- mapper = self._get_colormapper(cdim, element, ranges, style)
+ cmapper = self._get_colormapper(cdim, element, ranges, style)
data[cdim.name] = [] if empty else element.dimension_values(cdim)
mapping['color'] = {'field': cdim.name,
- 'transform': mapper}
+ 'transform': cmapper}
if 'hover' in self.tools+self.default_tools and not empty:
for d in dims:
diff --git a/holoviews/plotting/bokeh/path.py b/holoviews/plotting/bokeh/path.py
index e4cc8118cf..09930009ce 100644
--- a/holoviews/plotting/bokeh/path.py
+++ b/holoviews/plotting/bokeh/path.py
@@ -78,10 +78,10 @@ def get_data(self, element, ranges=None, empty=False):
if element.vdims and element.level is not None:
cdim = element.vdims[0]
- mapper = self._get_colormapper(cdim, element, ranges, style)
+ cmapper = self._get_colormapper(cdim, element, ranges, style)
data[cdim.name] = [] if empty else element.dimension_values(2)
mapping['fill_color'] = {'field': cdim.name,
- 'transform': mapper}
+ 'transform': cmapper}
if 'hover' in self.tools+self.default_tools:
dim_name = util.dimension_sanitizer(element.vdims[0].name)
From 9e2f1b631ba95d64b75303b4a4abd30c253d9ec2 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 14 Sep 2016 23:37:58 +0100
Subject: [PATCH 21/21] Added comment about colormapper instances
---
holoviews/plotting/bokeh/element.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index d567b93e52..d2eb5bc1d9 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -717,6 +717,9 @@ def _get_colormapper(self, dim, element, ranges, style):
palette = mplcmap_to_palette(style.pop('cmap', 'viridis'))
colormapper = LogColorMapper if self.logz else LinearColorMapper
cmapper = colormapper(palette, low=low, high=high)
+
+ # The initial colormapper instance is cached the first time
+ # and then updated with the values from new instances
if 'color_mapper' not in self.handles:
self.handles['color_mapper'] = cmapper
return cmapper