Datashader + Bokeh without (!) HoloViews

Yes you can. You can either use Datashader’s shade function to create an RGBA image and pass this to Bokeh’s figure.image_rgba, or you can pass the result of Datashader’s canvas.points call (or equivalent) to Bokeh’s figure.image and use an EqHistColorMapper (or Linear or Log color mapper) to do the shading in Bokeh. The latter allows you to easily add a colorbar.

Example output:

Code to generate this:

from bokeh.models import ColorBar, EqHistColorMapper
from bokeh.palettes import Spectral11
from bokeh.plotting import figure, row, show
import datashader as ds
import numpy as np
import pandas as pd

npoints = 100000
rng = np.random.default_rng(942852)
df = pd.DataFrame(dict(x=rng.normal(scale=0.25, size=npoints),
                       y=rng.normal(scale=0.25, size=npoints)))

canvas = ds.Canvas(100, 100, x_range=(-1, 1), y_range=(-1, 1))
agg = canvas.points(df, x="x", y="y")
im = ds.transfer_functions.shade(agg, cmap=list(Spectral11))

p0 = figure(width=420, height=400, title="Using Datashader.shade")
p0.image_rgba(image=[im.to_numpy()], x=-1, y=-1, dw=2, dh=2)

# Replace count of 0 with NaN so not rendered using color mapper.
agg = agg.to_numpy().astype(np.float64)
agg[agg==0.0] = np.nan

p1 = figure(width=500, height=400, title="Using Bokeh ColorMapper")
color_mapper = EqHistColorMapper(palette=Spectral11, nan_color="white")
p1.image(image=[agg], x=-1, y=-1, dw=2, dh=2, color_mapper=color_mapper)
color_bar = ColorBar(color_mapper=color_mapper)
p1.add_layout(color_bar, "right")

show(row(p0, p1))

The images look slightly different; there are changes in Bokeh 3.0 (due out very soon) that make Bokeh’s eq_hist colormapping more closely match Datashader’s. You cannot currently do categorical shading in Bokeh but there is work in progress (Categorical colormapping of 3D arrays by ianthomas23 · Pull Request #12356 · bokeh/bokeh · GitHub) to add this in the next few months.

Disadvantages of this approach are that the displayed resolution of the images is not the same as that produced by Datashader unless you manually twiddle it to account for Bokeh’s axis labels, etc. Also, when you zoom in you are just zooming into a static image. If these become important to you then should just use Holoviews; it is possible to implement resolution matching and zooming using Bokeh callbacks but then you are essentially writing your own version of Holoviews which is probably not a good use of time.

3 Likes