Repository: holoviz/holoviews

Issue:
Color levels not respected
#### ALL software version info
holoviews                 1.13.5                   pypi_0    pypi
Python 3.8.5 (default, Sep  4 2020, 02:22:02) [Clang 10.0.0 ] :: Anaconda, Inc. on darwin

#### Description of expected behavior and the observed behavior

When passing colour_levels to an image opts `color_levels` I would expect them to be respected eg. the colormap is broken on the limits that are specified.

As you can see in the example below the color_levels specifies `0` as a limit however the purple step includes values greater than 0. (In the data provided this is off the eastern African coast)


#### Complete, minimal, self-contained example code that reproduces the issue

data here: https://github.com/CEREGE-CL/netcdf_editor_app/blob/main/data/standard.nc
```
import xarray as xr
import holoviews as hv
import numpy
hv.extension('bokeh')

ds = xr.open_dataset('data/standard.nc')
var = 'Z'

abs_max = numpy.abs((ds[var].min(), ds[var].max())).max()

delta = 1000
clevels = [-abs_max] + [*numpy.arange(0, abs_max, delta)[::-1] * -1, * numpy.arange(0, abs_max, delta)[1:]] + [abs_max]
print('color_levels: ', clevels)

img = hv.Image(
    ds[var],
    group="Map",
).opts(
    invert_axes=True, tools=['hover'], width=600, aspect=2, colorbar=True, cmap='Category10', clim=(-abs_max, abs_max), color_levels = clevels
)

img

```

#### Screenshots or screencasts of the bug in action

![Screenshot 2021-04-19 at 10 42 18](https://user-images.githubusercontent.com/18068320/115207713-1f2a4a00-a0fc-11eb-9b23-0d7f7a48b800.png)



Discussion:
So it looks like this function is the culprit: https://github.com/holoviz/holoviews/blob/8e137c05db93844439144321e27ce8ab737cc9ba/holoviews/plotting/util.py#L924

Resolving patch:
diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py
--- a/holoviews/plotting/util.py
+++ b/holoviews/plotting/util.py
@@ -989,8 +989,7 @@ def color_intervals(colors, levels, clip=None, N=255):
     if clip is not None:
         clmin, clmax = clip
         lidx = int(round(N*((clmin-cmin)/interval)))
-        uidx = int(round(N*((cmax-clmax)/interval)))
-        uidx = N-uidx
+        uidx = len(cmap) - int(round(N*((cmax-clmax)/interval)))
         if lidx == uidx:
             uidx = lidx+1
         cmap = cmap[lidx:uidx]


Test patch:
diff --git a/holoviews/tests/plotting/bokeh/test_pathplot.py b/holoviews/tests/plotting/bokeh/test_pathplot.py
--- a/holoviews/tests/plotting/bokeh/test_pathplot.py
+++ b/holoviews/tests/plotting/bokeh/test_pathplot.py
@@ -131,7 +131,7 @@ def test_path_continuously_varying_color_op(self):
         self.assertEqual(source.data['date'], np.array([date]*3))
         self.assertEqual(cmapper.low, 994)
         self.assertEqual(cmapper.high, 999)
-        self.assertEqual(cmapper.palette, colors[-1:])
+        self.assertEqual(np.unique(cmapper.palette), colors[-1:])
 
     def test_path_continuously_varying_alpha_op(self):
         xs = [1, 2, 3, 4]
diff --git a/holoviews/tests/plotting/test_plotutils.py b/holoviews/tests/plotting/test_plotutils.py
--- a/holoviews/tests/plotting/test_plotutils.py
+++ b/holoviews/tests/plotting/test_plotutils.py
@@ -598,7 +598,7 @@ def test_color_intervals_clipped(self):
         colors = ['#5ebaff', '#00faf4', '#ffffcc', '#ffe775', '#ffc140', '#ff8f20', '#ff6060']
         cmap, lims = color_intervals(colors, levels, clip=(10, 90), N=100)
         self.assertEqual(cmap, ['#5ebaff', '#5ebaff', '#5ebaff', '#00faf4', '#00faf4',
-                                '#00faf4', '#00faf4', '#ffffcc'])
+                                '#00faf4', '#00faf4', '#ffffcc', '#ffffcc'])
         self.assertEqual(lims, (10, 90))