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.
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.
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()
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()
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.
.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.