diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index a19932fcce..d59d5969d4 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -1324,6 +1324,10 @@ class ColorbarPlot(ElementPlot): Number of discrete colors to use when colormapping or a set of color intervals defining the range of values to map each color to.""") + clim = param.NumericTuple(default=(np.nan, np.nan), length=2, doc=""" + User-specified colorbar axis range limits for the plot, as a tuple (low,high). + If specified, takes precedence over data and dimension ranges.""") + colorbar = param.Boolean(default=False, doc=""" Whether to display a colorbar.""") @@ -1405,7 +1409,10 @@ def _get_colormapper(self, eldim, element, ranges, style, factors=None, colors=N ncolors = None if factors is None else len(factors) if eldim: - if dim_name in ranges: + # check if there's an actual value (not np.nan) + if util.isfinite(self.clim).all(): + low, high = self.clim + elif dim_name in ranges: low, high = ranges[dim_name]['combined'] elif isinstance(eldim, dim): low, high = np.nan, np.nan @@ -1503,6 +1510,7 @@ def _get_color_data(self, element, ranges, style, name='color', factors=None, co if factors is not None and self.show_legend: mapping['legend'] = {'field': field} mapping[name] = {'field': field, 'transform': mapper} + return data, mapping @@ -1511,6 +1519,11 @@ def _get_cmapper_opts(self, low, high, factors, colors): colormapper = LogColorMapper if self.logz else LinearColorMapper if isinstance(low, (bool, np.bool_)): low = int(low) if isinstance(high, (bool, np.bool_)): high = int(high) + # Pad zero-range to avoid breaking colorbar (as of bokeh 1.0.4) + if low == high: + offset = self.default_span / 2 + low -= offset + high += offset opts = {} if util.isfinite(low): opts['low'] = low diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index 06233c9e6d..1dfc97004b 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -632,6 +632,10 @@ def teardown_handles(self): class ColorbarPlot(ElementPlot): + clim = param.NumericTuple(default=(np.nan, np.nan), length=2, doc=""" + User-specified colorbar axis range limits for the plot, as a tuple (low,high). + If specified, takes precedence over data and dimension ranges.""") + colorbar = param.Boolean(default=False, doc=""" Whether to draw a colorbar.""") @@ -775,6 +779,11 @@ def _norm_kwargs(self, element, ranges, opts, vdim, values=None, prefix=''): self.handles[prefix+'color_dim'] = vdim clim = opts.pop(prefix+'clims', None) + + # check if there's an actual value (not np.nan) + if clim is None and util.isfinite(self.clim).all(): + clim = self.clim + if clim is None: if not len(values): clim = (0, 0) diff --git a/holoviews/plotting/plotly/element.py b/holoviews/plotting/plotly/element.py index e099d7e818..bb809e30fa 100644 --- a/holoviews/plotting/plotly/element.py +++ b/holoviews/plotting/plotly/element.py @@ -361,6 +361,10 @@ def update_frame(self, key, ranges=None, element=None): class ColorbarPlot(ElementPlot): + clim = param.NumericTuple(default=(np.nan, np.nan), length=2, doc=""" + User-specified colorbar axis range limits for the plot, as a tuple (low,high). + If specified, takes precedence over data and dimension ranges.""") + colorbar = param.Boolean(default=False, doc=""" Whether to display a colorbar.""") @@ -390,7 +394,9 @@ def get_color_opts(self, eldim, element, ranges, style): if eldim: auto = False - if dim_name in ranges: + if util.isfinite(self.clim).all(): + cmin, cmax = self.clim + elif dim_name in ranges: cmin, cmax = ranges[dim_name]['combined'] elif isinstance(eldim, dim): cmin, cmax = np.nan, np.nan