Skip to main content

On This Page

Designing an Advanced Multi-Page Analytics Dashboard with Panel

4 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

How to Design an Advanced Multi-Page Interactive Analytics Dashboard with Dynamic Filtering, Live KPIs, and Rich Visual Exploration Using Panel

Asif Razzaq demonstrates a working implementation of a multi-page analytics dashboard using Panel, complete with live KPI updates and dynamic filtering. The system processes synthetic time-series data across segments and regions, enabling real-time exploration of traffic, conversions, and revenue.

Why This Matters

Real-world dashboards must balance interactivity with performance, but idealized models often overlook the complexity of dynamic filtering and live updates. This tutorial addresses these challenges by leveraging Panel’s reactive widgets and hvPlot’s visualization capabilities, ensuring updates occur without redundant computations. Failure to manage these aspects can lead to latency or incorrect data aggregation, costing hours in debugging.

Key Insights

  • “8-hour App Engine outage, 2012”: While not directly relevant, highlights the cost of poor real-time system design.
  • “Sagas over ACID for e-commerce”: Not applicable here, but underscores the need for flexible state management in dashboards.
  • “Panel used by [companies]”: Panel is widely adopted in data science for its integration with Python libraries like hvPlot and Bokeh.

Working Example

import sys, subprocess
def install_deps():
    pkgs = ["panel", "hvplot", "pandas", "numpy", "bokeh"]
    subprocess.check_call([sys.executable, "-m", "pip", "install", "-q"] + pkgs)
try:
    import panel as pn
    import hvplot.pandas
    import pandas as pd
    import numpy as np
except ImportError:
    install_deps()
    import panel as pn
    import hvplot.pandas
    import pandas as pd
    import numpy as np
pn.extension()
rng = np.random.default_rng(42)
dates = pd.date_range("2024-01-01", periods=365, freq="D")
segments = ["A", "B", "C"]
regions = ["North", "South", "East", "West"]
base = pd.DataFrame(
    {
        "date": np.tile(dates, len(segments) * len(regions)),
        "segment": np.repeat(segments, len(dates) * len(regions)),
        "region": np.repeat(np.tile(regions, len(segments)), len(dates)),
    }
)
base["traffic"] = (
    100
    + 40 * np.sin(2 * np.pi * base["date"].dt.dayofyear / 365)
    + rng.normal(0, 15, len(base))
)
trend = {"A": 1.0, "B": 1.5, "C": 2.0}
base["traffic"] *= base["segment"].map(trend)
base["conversions"] = (base["traffic"] * rng.uniform(0.01, 0.05, len(base))).astype(int)
base["revenue"] = base["conversions"] * rng.uniform(20, 60, len(base))
df = base.reset_index(drop=True)
segment_sel = pn.widgets.CheckBoxGroup(name="Segment", value=segments[:2], options=segments, inline=True)
region_sel = pn.widgets.MultiChoice(name="Region", value=["North"], options=regions)
metric_sel = pn.widgets.Select(name="Metric", value="traffic", options=["traffic", "conversions", "revenue"])
date_range = pn.widgets.DateRangeSlider(
    name="Date Range",
    start=df["date"].min(),
    end=df["date"].max(),
    value=(df["date"].min(), df["date"].max()),
)
smooth_slider = pn.widgets.IntSlider(name="Rolling Window (days)", start=1, end=30, value=7)

def filtered_df(segment, region, drange):
    d1, d2 = drange
    mask = (
        df["segment"].isin(segment)
        & df["region"].isin(region or regions)
        & (df["date"] >= d1)
        & (df["date"] <= d2)
    )
    sub = df[mask].copy()
    if sub.empty:
        return df.iloc[:0]
    return sub

@pn.depends(segment_sel, region_sel, metric_sel, smooth_slider, date_range)
def timeseries_plot(segment, region, metric, window, drange):
    data = filtered_df(segment, region, drange)
    if data.empty:
        return pn.pane.Markdown("### No data for current filters")
    grouped = data.sort_values("date").groupby("date")[metric].sum()
    line = grouped.hvplot.line(title=f"{metric.title()} over time", ylabel=metric.title())
    if window > 1:
        smooth = grouped.rolling(window).mean().hvplot.line(line_width=3, alpha=0.6)
        return (line * smooth).opts(legend_position="top_left")
    return line
@pn.depends(segment_sel, region_sel, metric_sel, date_range)
def segment_bar(segment, region, metric, drange):
    data = filtered_df(segment, region, drange)
    if data.empty:
        return pn.pane.Markdown("### No data to aggregate")
    agg = data.groupby("segment")[metric].sum().sort_values(ascending=False)
    return agg.hvplot.bar(title=f"{metric.title()} by Segment", yaxis=None)

@pn.depends(segment_sel, region_sel, metric_sel, date_range)
def region_heatmap(segment, region, metric, drange):
    data = filtered_df(segment, region, drange)
    if data.empty:
        return pn.pane.Markdown("### No data to aggregate")
    pivot = data.pivot_table(index="segment", columns="region", values=metric, aggfunc="sum")
    return pivot.hvplot.heatmap(title=f"{metric.title()} Heatmap", clabel=metric.title())
kpi_source = df.copy()
kpi_idx = [0]
def compute_kpi(slice_df):
    if slice_df.empty:
        return 0, 0, 0
    total_rev = slice_df["revenue"].sum()
    avg_conv = slice_df["conversions"].mean()
    cr = (slice_df["conversions"].sum() / slice_df["traffic"].sum()) * 100
    return total_rev, avg_conv, cr

kpi_value = pn.indicators.Number(name="Total Revenue (window)", value=0, format="$0,0")
conv_value = pn.indicators.Number(name="Avg Conversions", value=0, format="0.0")
cr_value = pn.indicators.Number(name="Conversion Rate", value=0, format="0.00%")

def update_kpis():
    step = 200
    start = kpi_idx[0]
    end = start + step
    if start >= len(kpi_source):
        kpi_idx[0] = 0
        start, end = 0, step
    window_df = kpi_source.iloc[start:end]
    kpi_idx[0] = end
    total_rev, avg_conv, cr = compute_kpi(window_df)
    kpi_value.value = total_rev
    conv_value.value = avg_conv
    cr_value.value = cr / 100

pn.state.add_periodic_callback(update_kpis, period=1000, start=True)
controls = pn.WidgetBox(
    "### Global Controls",
    segment_sel,
    region_sel,
    metric_sel,
    date_range,
    smooth_slider,
    sizing_mode="stretch_width",
)
page_overview = pn.Column(
    pn.pane.Markdown("## Overview: Filtered Time Series"),
    controls,
    timeseries_plot,
)
page_insights = pn.Column(
    pn.pane.Markdown("## Segment & Region Insights"),
    pn.Row(segment_bar, region_heatmap),
)
page_live = pn.Column(
    pn.pane.Markdown("## Live KPI Window (simulated streaming)"),
    pn.Row(kpi_value, conv_value, cr_value),
)
dashboard = pn.Tabs(
    ("Overview", page_overview),
    ("Insights", page_insights),
    ("Live KPIs", page_live),
)
dashboard

Practical Applications

  • Use Case: Data analysts using Panel to build real-time dashboards for monitoring e-commerce metrics.
  • Pitfall: Overloading widgets with too many dependencies, causing lag in updates.

References:

Continue reading

Next article

Optimizing LLM Training with AdamW and Cosine Decay

Related Content