Legend for gridded Holoviews visualization with categorical data

365 views Asked by At

I'm using the holoviz xarray extension (holoviews.xarray) to visualize a gridded dataset with landcover classes. Plotting the data is straightforward with da.hvplot(). This results however in a continuous colormap with standard tick labels, whereas I need the categories plotted using a specific colormap and their labels included in a legend.

So how can I plot gridded categorical data using Holoviews? My plot needs to:

  • Have the categories plotted according to a specific colormap (hex color codes).
  • Include a legend with labels ["water", "cirrus", ...].
  • Handle situations where the data do not contain all classes. Explanation, when using da.hvplot(cmap=tuple(color_key.values()) while da does not contain all classes this typically results in a plot where the colorbar ticks do not match the color classes.
  • Have a legend outside the plotted data.

The best I got so far is the example provided below. But how can I move that legend out of the plot? Or is there a more straightforward solution?

import holoviews as hv
import hvplot.xarray
import numpy as np
import xarray as xr

color_key = {
    "No Data": "#000000",
    "Saturated / Defective": "#ff0000",
    "Dark Area Pixels": "#2f2f2f",
    "Cloud Shadows": "#643200",
    "Vegetation": "#00a000",
    "Bare Soils": "#ffe65a",
    "water": "#0000ff",
    "Clouds low probability / Unclassified": "#808080",
    "Clouds medium probability": "#c0c0c0",
    "Clouds high probability": "#ffffff",
    "Cirrus": "#64c8ff",
    "Snow / Ice": "#ff96ff",
}


# Generate sample data
nx = 40
ny = 70

xcoords = [37 + 0.1 * i for i in range(nx)]
ycoords = [5 + 0.2 * i for i in range(ny)]

data = np.random.randint(low=0, high=len(color_key), size=nx * ny).reshape(nx, ny)
da = xr.DataArray(
    data,
    dims=["x", "y"],
    coords={"x": xcoords, "y": ycoords},
)

# Visualization
legend = hv.NdOverlay(
    {
        k: hv.Points([0, 0], label=f"{k}").opts(color=v, size=0, apply_ranges=False)
        for k, v in color_key.items()
    },
    "Classification",
)

da.hvplot().opts(cmap=tuple(color_key.values())) * legend
1

There are 1 answers

2
philippjfr On

You could either set .opts(legend_location='right') OR you can override the actual ticks on the colorbar using the colorbar_opts option and by providing a fixed ticker along with major_label_overrides like this:

ticks = np.arange(len(color_key), dtype='float') + 0.0001
ticker = FixedTicker(ticks=ticks)
labels = dict(zip(ticks, color_key))

da.hvplot(height=600).opts(clim=(-0.5, 11.5), cmap=tuple(color_key.values()), colorbar_opts={'ticker': ticker, 'major_label_overrides': labels})

enter image description here