Updating Datashader with inputs

Hi Dash/Holoviews Experts,
Could you help find way to update Datashader graph in Dash app while maintaining the interactive behavior? The obvious way is to generate the object inside callback function and then pass it to html.Div via to_dash() function. The issue is that on zoom graph would not recreate the image and instead enlarge the same raster image which makes it useless in my scenario. It was well described in the question but not yet answered. Would you have any idea how to fix it using callbacks or solve in a different way? The objective is simple: to have a datashader plot that can be controlled with inputs (changing dataset, colors, etc).

BR,
Marcin

Have you tried the HoloViews / Dash integration described at HoloViews | Dash for Python Documentation | Plotly ? HoloViews wraps up a data object with an event-callback structure that allows for the rendering to be updated on zoom, etc.

Hi @jbednar. Thanks for taking the time to answer my question. I surely tried the docs. The article describes only the scenario when datashader is created in the body of the dash script. Putting the same code in a dash callback changes the datashader behavior. I’ve made a basic example to compare both: datashader1 works fine, datashader2 doesn’t regenerate the image on zoom.
Any ideas how change this code to allow datashader react to user inputs without breaking the functionality?

from dash import Dash, html, dcc, Output, Input
from sklearn.datasets import make_classification
import pandas as pd
from holoviews import Dataset, Scatter
from holoviews.plotting.plotly.dash import to_dash
from holoviews.operation.datashader import datashade


def generate_data():
    X, y = make_classification(n_samples=50000, n_features=2, n_redundant=0)
    df = pd.DataFrame(X, columns=["x1", "x2"])
    df["y"] = y
    return df


dataset = Dataset(generate_data(), kdims=["x1"], vdims=["x2"])
datashader1 = datashade(Scatter(dataset)).opts(
    title="Datashader in Dash body (zoom generates new image)"
)

app = Dash(__name__, suppress_callback_exceptions=True)
components = to_dash(app, [datashader1], reset_button=True)

app.layout = html.Div(
    [
        html.Div(id="scatter1", children=components.children),
        html.Div(id="scatter2"),
        dcc.RadioItems(id="radio", options=["red", "blue"], value="red"),
    ],
    style={
        "display": "grid",
        "grid-template-columns": "700px 700px auto",
    },
)


@app.callback(Output("scatter2", "children"), Input("radio", "value"))
def generate_scatter(value):
    scatter2 = datashade(Scatter(dataset), cmap=value).opts(
        title="Datashader in callback (zoom enlarges same image)"
    )
    components = to_dash(app, [scatter2], reset_button=True)
    return components.children


if __name__ == "__main__":
    app.run(debug=True)

Hopefully someone who uses Dash can reply; I’m not sure how callbacks are handled there.

Thanks. I wish a dash-ninja will come up with a solution. I considered rewriting to_dash implementation but that is a too complex task for my case. Recreating a callback manually doesn’t work as the objects get uniquely generated names. Still there might by other solutions that are not obvious to me …