Hi, I would like to create a more sophisticated dashboard. However, I’m having trouble combining the param.Parametrized
and pn.viewable.Viewer
classes with non-blocking loading of data and, effectively, the entire page.
- I want to display the page before the data and components are loaded. Instead of waiting, I’d like to show a
LoadingSpinner
to indicate that the data is being fetched. Only when the components are created should they be progressively displayed. - I also don’t quite understand how, in this scenario, to decouple the data from the
date_range
andViewer
components. - I would like to add a
DateRangeSlider
to complement theDateRangePicker
, but I’m not sure how to usepn.rx
for this. - I don’t even know how to structure the project in this case.
Here is my current (blocking) code draft, I have no Idea how to change it to make it non-blocking. Tutorials usually use functions instead of classes.
import param
import panel as pn
import datetime as dt
import time
import hvplot.polars
import polars as pl
import numpy as np
## === Data manipulation ===
def get_data_1(date_from: str, date_to: str) -> pl.DataFrame:
"""Some data from SQL db (time consuming function), Returns polars df. For simplicity generate simple random data."""
time.sleep(3)
date_series = pl.date_range(
start=pl.lit(date_from),
end=pl.lit(date_to),
eager=True,
)
n = len(date_series)
x = 50 + np.random.randn(n).cumsum()
d = np.random.randn(n)
df = pl.DataFrame(
{
"dt": date_series,
"x": x,
"d": d,
}
)
return df
def get_data_1_available_date_range() -> tuple[dt.date, dt.date]:
"""SQL query to db."""
time.sleep(0.5)
# use to bound date_range and picker/slider
first_date = dt.date(2025, 1, 1)
last_date = dt.date(2025, 3, 31)
return (first_date, last_date)
def postprocess_data_data_1(df: pl.DataFrame):
some_stats = df.select(
pl.min("dt").alias("first"),
pl.max("dt").alias("last"),
pl.mean("x"),
pl.mean("d"),
)
return some_stats
def get_data_2(date_from: str, date_to: str):
"""Another data source - time consuming source (REST API). For simplicity generate simple random data."""
time.sleep(5)
df = pl.DataFrame(
{
"i": list(range(100)),
"perc": np.random.randint(0, 100, size=100),
}
)
return df
## === Charts ===
def create_sophisticate_charts(df: pl.DataFrame):
"""Another time consuming task (chart generation)."""
time.sleep(1)
chart1 = df.hvplot.line(x="dt", y="x")
distribution_chart = df.hvplot.hist("d")
chart_layout = chart1 + distribution_chart
return chart_layout
def create_chart_2(df: pl.DataFrame):
"""This is another sophisticated chart with consumed a lot of time."""
time.sleep(3)
barchart = df.hvplot.bar(x="i", y="perc")
return barchart
## === Components ===
class Component1(pn.viewable.Viewer):
"""Chart + Stats info."""
def __init__(self, date_from: str, date_to: str):
# super.__init__(self, *args, **kwargs)
self.df = get_data_1(date_from, date_to)
self.stats_df = postprocess_data_data_1(self.df)
def __panel__(self):
return pn.Column(
pn.Card(
pn.pane.Markdown(repr(self.stats_df)),
title="Total stats",
width=400,
),
create_sophisticate_charts(self.df),
)
class Component2(pn.viewable.Viewer):
"""Sophisticated chart."""
def __init__(self, date_from: str, date_to: str):
self.df = get_data_2(date_from, date_to)
def __panel__(self):
return pn.panel(create_chart_2(self.df))
## === Pages ===
class PageContent1(pn.viewable.Viewer):
date_range = param.DateRange(default=(dt.date(2025, 1, 1), dt.date(2025, 2, 10)))
def __init__(self, **params):
super().__init__(**params)
# set bounds for self.date_range and this date_range_picker
# after time-consuming operation get_data_1_available_date_range()
date_from, date_to = get_data_1_available_date_range()
self.date_from = date_from
self.date_to = date_to
self.param.date_range.bounds = (date_from, date_to)
self.date_range_picker = pn.widgets.DateRangePicker(
name="Date Range Picker",
value=(dt.date(2025, 1, 1), dt.date(2025, 2, 10)),
start=date_from,
end=date_to,
)
# Link the picker to the parameter
self.date_range_picker.link(self, value="date_range")
# Initialize components with default values
self.component1 = Component1("2025-01-01", "2025-02-10")
self.component2 = Component2("2025-01-01", "2025-02-10")
@param.depends("date_range", watch=True)
def _update_charts(self):
# Convert date range to strings - use getattr for safety
date_range_value = getattr(
self, "date_range", (dt.date(2025, 1, 1), dt.date(2025, 2, 10))
)
start_date, end_date = date_range_value
date_from_str = start_date.strftime("%Y-%m-%d")
date_to_str = end_date.strftime("%Y-%m-%d")
self.component1 = Component1(date_from_str, date_to_str)
self.component2 = Component2(date_from_str, date_to_str)
def __panel__(self):
return pn.Column(
self.date_range_picker, pn.FlexBox(self.component1, self.component2)
)
# dashboard
p = PageContent1()
dashboard = pn.template.BootstrapTemplate(
title="My Dashboard",
main=[p],
sidebar_width=250,
sidebar=[
"## Navigation",
],
)
Can you help me?