HoloViews + Panel

Build dashboards with HoloViews and Panel in your browser: dropdowns that pick what to plot, sliders that filter points and auto-made widgets.

Python
import holoviews as hv
import pandas as pd
import panel as pn
import panel.widgets as pnw

hv.extension("bokeh")
df = pd.DataFrame(
    {
        "horsepower": [95, 110, 140, 165, 88, 120],
        "mpg": [30, 27, 22, 18, 34, 25],
        "weight": [2100, 2350, 2800, 3200, 1900, 2600],
        "origin": ["Europe", "Japan", "USA", "USA", "Japan", "Europe"],
    }
)

columns = sorted(df.columns)
discrete = [x for x in columns if df[x].dtype == object]
continuous = [x for x in columns if x not in discrete]
quantileable = continuous

x = pnw.Select(name="X-Axis", value="horsepower", options=quantileable)
y = pnw.Select(name="Y-Axis", value="mpg", options=quantileable)
size = pnw.Select(name="Size", value="weight", options=["None"] + quantileable)
color = pnw.Select(name="Color", value="origin", options=discrete)

@pn.depends(x.param.value, y.param.value, color.param.value, size.param.value)
def create_figure(x, y, color, size):
    opts = dict(
        cmap="Category10",
        width=700,
        height=450,
        line_color="black",
        tools=["hover"],
        color=color,
    )
    if size != "None":
        opts["size"] = hv.dim(size).norm() * 25 + 5
    return hv.Points(df, [x, y], label=f"{x.title()} vs {y.title()}").opts(**opts)

widgets = pn.WidgetBox(x, y, color, size, width=200)

pn.Row(widgets, create_figure).servable()

HoloViews builds plots from data and column names. You wrap a table in an element such as hv.Points, hv.Curve or hv.Bars, name the columns to use, and HoloViews draws it with Bokeh, Matplotlib or Plotly. Panel adds widgets and layout, so together they make dashboards where dropdowns and sliders decide what a plot shows. On this page it runs in your browser, so you can try it without installing anything. Run the example first, then paste any snippet below into a new cell to try it.

What the example does

hv.extension("bokeh") makes Bokeh the plotting backend. The DataFrame holds six cars. The code sorts the column names and splits them by type: origin holds text, so it is the only discrete column and the only choice in the Color dropdown. horsepower, mpg and weight are continuous. Four Select widgets pick the x, y, color and size columns. @pn.depends(...) ties create_figure() to the four widget values, and Panel calls it again when any of them changes. hv.Points(df, [x, y]) plots the two chosen columns, and the other columns come along as value dimensions, so the hover tool lists all four for each point. .opts() sets the style: color gives each origin its own Category10 color, and hv.dim(size).norm() * 25 + 5 turns the size column into point sizes from 5 to 30 pixels. pn.WidgetBox groups the widgets, pn.Row puts them left of the plot, and .servable() marks the row as the app, which appears below the cell when it runs.

Pick a column with a dropdown

pn.bind() connects widgets to a plain function, without a decorator. hv.Bars(df, "month", metric) takes the category column and the value column:

import holoviews as hv
import pandas as pd
import panel as pn

hv.extension("bokeh")
df = pd.DataFrame({
    "month": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
    "visitors": [820, 760, 990, 1120, 1300, 1250],
    "signups": [41, 35, 52, 60, 77, 70],
})

metric = pn.widgets.Select(name="Metric", options=["visitors", "signups"])

def bars(metric):
    return hv.Bars(df, "month", metric).opts(width=500, height=300, tools=["hover"])

pn.Row(metric, pn.bind(bars, metric)).servable()

Filter points with a range slider

A RangeSlider value is a (low, high) tuple. Fixed xlim and ylim keep the axes still while the number of points changes:

import holoviews as hv
import numpy as np
import pandas as pd
import panel as pn

hv.extension("bokeh")
rng = np.random.default_rng(seed=0)
df = pd.DataFrame({"x": rng.normal(size=300), "y": rng.normal(size=300)})
df["distance"] = np.hypot(df["x"], df["y"])

limits = pn.widgets.RangeSlider(name="Distance from center", start=0, end=4.5,
                                step=0.1, value=(0, 4.5))

def points(limits):
    subset = df[df["distance"].between(*limits)]
    return hv.Points(subset, ["x", "y"]).opts(
        width=400, height=400, xlim=(-4, 4), ylim=(-4, 4), title=f"{len(subset)} points")

pn.Row(limits, pn.bind(points, limits)).servable()

Let HoloViews make the widgets

A DynamicMap calls a function for each combination of its key dimensions. Give each dimension a range, and pn.panel() adds a slider for it:

import holoviews as hv
import numpy as np
import panel as pn

hv.extension("bokeh")
x = np.linspace(0, 2 * np.pi, 200)

def wave(frequency, phase):
    return hv.Curve((x, np.sin(frequency * x + phase))).opts(
        width=500, height=300, ylim=(-1.1, 1.1))

dmap = hv.DynamicMap(wave, kdims=["frequency", "phase"])
dmap = dmap.redim.range(frequency=(1.0, 5.0), phase=(0.0, 3.0))
pn.panel(dmap).servable()

Float ranges give float sliders. With (1, 5), Panel makes an integer slider that moves in whole steps.

Good to know

  • End the layout with .servable(). A Panel object left on the last line, the way Jupyter shows one, displays an error here instead of the app.
  • hv.dim(column).norm() scales a column to the range 0 to 1, so the smallest value gets size 0. The example adds 5 so that every point keeps a visible size.
  • The app runs in its own copy of Python, which it loads when it starts. It cannot use variables or imports from other cells, so keep everything the app needs in one cell.