Holoviews Heatmap specify color for each point

1.5k Views Asked by At

I'm trying to generate a Heatmap in Holoviews with custom color values for each point, in order to specify different alpha color values.

Currently I'm generating two Holoviews plots with different alpha values and overlay them like so:

data = pd.DataFrame([(i, 97+j,  i*j) for i in range(5) for j in range(5)], 
                columns=['x', 'y', 'val'])
data_filtered = data[(data.x < 3) & (data.y < 100)]

hm_opts = dict(kdims=['x', 'y'], vdims=['val'])
hm = hv.HeatMap(data, **hm_opts).opts(alpha=0.5)
hm_filtered = hv.HeatMap(data_filtered, **hm_opts).opts()
hm * hm_filtered

Plot

This however, gives me a Holoviews Overlay object, where I'd like to have a single HeatMap object instead.

My idea was to map each x/y coordinate to a specific color value in hex form, which has the needed alpha already calculated into it. My DataFrame would therefore look something like this (example):

    x    y  val    color
0   0   97    0  #00FF00
1   0   98    0  #00FF00
2   0   99    0  #00FF00
...
22  4   99    8  #FFD29F
23  4  100   12  #FFB89F
24  4  101   16  #D3AFF4

Is there a way to tell Holoviews to use these colors? When I pass the list of colors to the "cmap" parameter it interpretes it as color intervals, passing the name of the column raises an error as it cannot find the specified cmap.

When I pass the list to the "color" parameter, the plot is not displayed at all anymore in the Jupyter Notebook.

Edit

I found a way to get what I want using the Bokeh library directly. Bokeh is also the backend I'm using with Holviews. This is the code and the resulting plot.

source = ColumnDataSource(
    data=data
)
x_unique = data['x'].unique()
y_unique = data['y'].unique()
min_width = 110
min_height = 80
width = min_width + 25 * len(x_unique)
height = min_height + 25 * len(y_unique)
x_rect_width = 0.90
y_rect_width = 0.90
plot = figure(
    plot_width=width,
    plot_height=height,
    title='',
    x_range=FactorRange(*x_unique),
    y_range=FactorRange(*y_unique),
    x_axis_label='x',
    y_axis_label='y',
)
plot.rect('x', 'y', height=y_rect_width, width=x_rect_width, source=source, color='color')
plot.xgrid.grid_line_color = None
plot.ygrid.grid_line_color = None

show(plot)

Plot2

Bokeh allows to pass the color column name to the 'color' parameter of the rect function. Anyway, I'd still love to have it as a Holoviews container, so that I can combine it and build interactive apps on top of it.

Solution

With the help of the answer of @thomas-pedot I was able to find a solution which looks like what I was looking for:

data = pd.DataFrame([(i, 97+j,  i*j) for i in range(5) for j in range(5)], 
                    columns=['x', 'y', 'val'])
data = data.assign(alpha=((data.x < 3) & (data.y < 100)).replace({True: 1.0, False: 3/8}))
red = '#FF0000'
yellow = '#FFFF00'
green = '#00FF00'
blue_violet = '#8A2BE2'
max_cout = data.loc[:, column].max()
levels = [0, 1, max_cout / 2, max_cout - 1, max_cout]
colors = [green, yellow, red, blue_violet]
hm_opts = dict(kdims=['x', 'y'], vdims=['val', 'alpha'])
hm = hv.HeatMap(data, **hm_opts).opts(
  alpha=hv.dim('alpha'), 
  color_levels=levels, 
  cmap=colors)
hm

Solution

2

There are 2 best solutions below

4
On BEST ANSWER

Not easy... I try a lot of different converting to rgba then back to HEX cmap without success but !

Make it with style http://holoviews.org/user_guide/Style_Mapping.html

data = pd.DataFrame([(i, 97+j,  i*j) for i in range(5) for j in range(5)], 
                columns=['x', 'y', 'val'])
data['a_new'] = 0.5
# Make a mask of you filter
mask = (data.x < 3) & (data.y < 100)

# Add it to a column
data1.loc[mask, 'a_new'] = 1.0 

#Add the new column to a new dimension
hm_opts_new = dict(kdims=['x', 'y'], vdims=['val','a_new'])

#Enjoy ? I hope... set the alpha option depends on your new column

hv.HeatMap(data1, **hm_opts_new).opts(opts.HeatMap(tools=['hover'], alpha=dim('a_new')))
3
On
import pandas as pd
import holoviews as hv 
import matplotlib as mpl
hv.extension('matplotlib')

data = pd.DataFrame([(i, 97+j,  i*j) for i in range(5) for j in range(5)], 
                columns=['x', 'y', 'val'])
data_filtered = data[(data.x < 3) & (data.y < 100)]

cmap1 = mpl.colors.ListedColormap(['#00FF00', '#FFB89F', '#D3AFF4'])
hm_opts = dict(kdims=['x', 'y'], vdims=['val'])
hm = hv.HeatMap(data, **hm_opts).opts(alpha=0.5, cmap=cmap1)

results:

results

I added the line cmap1 = mpl.colors.ListedColormap(['#00FF00', '#FFB89F', '#D3AFF4']) which allows you to specifiy which colors you want. If you update it to a list of colors (a list in range of your whole heatmap) then it will be exactly as you want. I guess you can work with df["color"] in your case (maybe directly convert to list first, but it will be alright)

Notice I also added cmap=cmap1 param to heatmap' opt.

I assume you work with matplotlib in background, since you didn't specify otherwise.