Hi All,
I’ve created multiple real time dashboards in panel and each one works individually exactly as designed. The dashboards display real time data including pitch, roll, yaw, alt, lat, long, etc. using the full range of Holoviz/Panel indicators and widgets.
When I try and run them together using --glob navigation between apps breaks the background process and it shuts down.
Closed a session running at the / endpoint
I’ve previously created a topic 7346 and I’ve taken this example and updated it for my latest issue. Here a quick and dirty MRE.
app.py
from asyncio import sleep
import holoviews as hv
import pandas as pd
import panel as pn
from holoviews.streams import Buffer
import hvplot.pandas # no qa
pn.extension()
data = pn.state.cache["data"]
dfstream = Buffer(pd.DataFrame(data, index=[pd.Timestamp.now()]), length=100, index=False)
def plot(data, window_seconds, alpha):
data = data.rolling(f"{window_seconds}s").mean()
return data.hvplot(y="y", ylim=(0, 50), alpha=alpha, color="blue", line_width=5)
window_seconds = pn.widgets.IntSlider(value=5, start=1, end=10, name="Window (secs)")
alpha = pn.widgets.FloatSlider(value=1, start=0, end=1, name="Alpha")
iplot = hv.DynamicMap(
plot,
streams={
"data": dfstream.param.data,
"window_seconds": window_seconds.param.value,
"alpha": alpha.param.value,
},
)
pages = {
"Page 1": pn.Column(iplot, window_seconds, alpha),
"Page 2": pn.Column("# Page 2", "...more bla"),
}
def show(page):
return pages[page]
starting_page = pn.state.session_args.get("page", [b"Page 1"])[0].decode()
page = pn.widgets.RadioButtonGroup(
value=starting_page,
options=list(pages.keys()),
name="Page",
sizing_mode="fixed",
button_type="success",
)
ishow = pn.bind(show, page=page)
pn.state.location.sync(page, {"value": "page"})
ACCENT_COLOR = "#0072B5"
DEFAULT_PARAMS = {
"site": "Panel Multi Page App",
"accent_base_color": ACCENT_COLOR,
"header_background": ACCENT_COLOR,
}
pn.template.FastListTemplate(
title="As Single Page App",
sidebar=[page],
main=[ishow],
**DEFAULT_PARAMS,
).servable()
async def run():
while True:
await sleep(0.1)
data = pn.state.cache["data"]
dfstream.send(pd.DataFrame(data, index=[pd.Timestamp.now()]))
pn.state.onload(run)
app2.py
from asyncio import sleep
import holoviews as hv
import pandas as pd
import panel as pn
from holoviews.streams import Buffer
import hvplot.pandas # no qa
pn.extension()
data = pn.state.cache["data"]
dfstream = Buffer(pd.DataFrame(data, index=[pd.Timestamp.now()]), length=100, index=False)
def plot(data, window_seconds, alpha):
data = data.rolling(f"{window_seconds}s").mean()
return data.hvplot(y="a", ylim=(0, 50), alpha=alpha, color="blue", line_width=5)
window_seconds = pn.widgets.IntSlider(value=5, start=1, end=10, name="Window (secs)")
alpha = pn.widgets.FloatSlider(value=1, start=0, end=1, name="Alpha")
iplot = hv.DynamicMap(
plot,
streams={
"data": dfstream.param.data,
"window_seconds": window_seconds.param.value,
"alpha": alpha.param.value,
},
)
pages = {
"Page 1": pn.Column(iplot, window_seconds, alpha),
"Page 2": pn.Column("# Page 2", "...more bla"),
}
def show(page):
return pages[page]
starting_page = pn.state.session_args.get("page", [b"Page 1"])[0].decode()
page = pn.widgets.RadioButtonGroup(
value=starting_page,
options=list(pages.keys()),
name="Page",
sizing_mode="fixed",
button_type="success",
)
ishow = pn.bind(show, page=page)
pn.state.location.sync(page, {"value": "page"})
ACCENT_COLOR = "#0072B5"
DEFAULT_PARAMS = {
"site": "Panel Multi Page App",
"accent_base_color": ACCENT_COLOR,
"header_background": ACCENT_COLOR,
}
pn.template.FastListTemplate(
title="As Single Page App",
sidebar=[page],
main=[ishow],
**DEFAULT_PARAMS,
).servable()
async def run():
while True:
await sleep(0.1)
data = pn.state.cache["data"]
dfstream.send(pd.DataFrame(data, index=[pd.Timestamp.now()]))
pn.state.onload(run)
index.py
"""
Panel App Gallery
A Panel-based index page that serves as a gallery of Panel applications with search and pagination functionality.
This demonstrates how to use `panel serve *.py --index index.py` to create a custom Python-based index page
instead of relying on HTML/Jinja templates.
Features:
- Real-time search functionality that filters apps by name and description
- Responsive thumbnails and app metadata display
- Pagination support when there are more than APPS_PER_PAGE results
- Modern Design styling with hover effects
- Auto-discovery of Panel apps in the current directory
Usage:
panel serve *.py --index index.py --dev --show
This app provides an interactive way to browse and search through a collection of Panel applications,
making it easy to find specific apps in larger collections. Each app displays its name, description,
and thumbnail image with direct links to launch the application.
"""
import math
from typing import List
import panel as pn
import param
# Configuration
APPS_PER_PAGE = 20
ACCENT_COLOR = "#4099da"
ACCENT_FILL_HOVER = "#0072b5"
# Initialize Panel with required extensions
pn.extension("tabulator", sizing_mode="stretch_width")
class AppInfo:
"""Data class to hold information about a Panel app"""
def __init__(self, name: str, filename: str, description: str = "", thumbnail: str = ""):
self.name = name
self.filename = filename
self.description = description or f"Panel application: {name}"
self.thumbnail = thumbnail or self._get_default_thumbnail()
def _get_default_thumbnail(self) -> str:
"""Generate a default thumbnail placeholder"""
return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDIwMCAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik04NyA2NUg5M1Y1OUg4N1Y2NVoiIGZpbGw9IiM5Q0E4QjciLz4KPHA5YXRoIGQ9Ik05NyA2MUgxMTNWODdIOTdWNjFaIiBmaWxsPSIjOUNBOEI3Ii8+CjxwYXRoIGQ9Ik04MyA3NEgxMTdWNzlIODNWNzRaIiBmaWxsPSIjOUNBOEI3Ii8+CjxwYXRoIGQ9Ik04MyA4Mkg5OFY4N0g4M1Y4MloiIGZpbGw9IiM5Q0E4QjciLz4KPC9zdmc+Cg=="
def url(self) -> str:
"""Generate the URL to launch the app"""
return f"./{self.filename.replace('.py', '')}"
class AppGallery(pn.viewable.Viewer):
"""
A Panel-based gallery for browsing and searching Panel applications.
This component provides:
- Search functionality with real-time filtering
- Pagination for large app collections
- Thumbnail previews and app metadata
- Direct links to launch applications
"""
# Parameters for state management
search_term = param.String(default="", doc="Current search term")
current_page = param.Integer(default=1, bounds=(1, None), doc="Current page number")
apps_per_page = param.Integer(
default=APPS_PER_PAGE, bounds=(1, 50), doc="Number of apps per page"
)
def __init__(self, **params):
super().__init__(**params)
# Discover available Panel apps
self.all_apps = self._discover_apps()
# Create UI components
self._create_ui()
def _discover_apps(self) -> List[AppInfo]:
"""Discover Panel apps in the current directory"""
apps = []
# Add some demo apps for illustration
demo_apps = [
AppInfo(
"App",
"app.py",
"Interactive dashboard for...",
thumbnail=r".\assets\images\thumbnails\app.png",
),
AppInfo(
"App 2",
"app2.py",
"Interactive dashboard for...",
thumbnail=r".\assets\images\thumbnails\app2.png",
),
]
apps.extend(demo_apps)
return sorted(apps, key=lambda x: x.name)
def _create_ui(self):
"""Create the user interface components"""
with pn.config.set(sizing_mode="stretch_width"):
# Header
self._header = pn.pane.HTML("""
<div style="text-align: center; padding: 20px 0;">
<h1 style="color: #2E86AB; margin-bottom: 10px;">Holoviz</h1>
<h1 style="color: #2E86AB; margin-bottom: 5px;">Real Time Data Analytics</h1>
<p style="color: #666; font-size: 18px;">Discover and explore applications</p>
</div>
""")
# Search bar
self._search_input = pn.widgets.TextInput.from_param(
self.param.search_term,
placeholder="Search apps by name or description...",
margin=(10, 20),
sizing_mode="stretch_width",
)
# Apps grid container
self._apps_container = pn.FlexBox(
sizing_mode="stretch_width",
align="center",
)
# Pagination controls
self._pagination = pn.Row(sizing_mode="stretch_width", styles={"margin": "0 auto"})
# Main layout
self._layout = pn.Column(
self._header,
self._search_input,
self._apps_container,
pn.Spacer(sizing_mode="stretch_height"),
self._pagination,
max_width=975,
styles={"margin": "0 auto"},
align="center",
sizing_mode="stretch_both",
)
# Initial update will be triggered automatically by parameter reactivity
self._update_display()
@param.depends("search_term", watch=True)
def _on_search_change(self):
"""Handle search term changes by resetting to first page"""
self.current_page = 1
def _get_filtered_apps(self) -> List[AppInfo]:
"""Get filtered apps based on search term"""
# Filter apps based on search term
search_term = str(self.search_term or "")
if search_term:
filtered_apps = [
app
for app in self.all_apps
if (
search_term.lower() in app.name.lower()
or search_term.lower() in app.description.lower()
)
]
else:
filtered_apps = self.all_apps
return filtered_apps
def _get_paginated_apps(self, apps: List[AppInfo]) -> List[AppInfo]:
"""Get apps for current page"""
# Safely get parameter values with defaults
current_page = max(1, getattr(self, "current_page", 1) or 1)
apps_per_page = max(1, getattr(self, "apps_per_page", APPS_PER_PAGE) or APPS_PER_PAGE)
start_idx = (current_page - 1) * apps_per_page
end_idx = start_idx + apps_per_page
return apps[start_idx:end_idx]
def _create_app_card(self, app: AppInfo) -> pn.pane.HTML:
"""Create a card for a single app"""
card_html = f"""
<div style="
border: 1px solid var(--neutral-stroke-rest);
border-radius: 12px;
padding: 20px;
margin: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.3s ease;
height: 320px;
display: flex;
flex-direction: column;
" onmouseover="this.style.transform='translateY(-5px)'; this.style.boxShadow='0 8px 25px rgba(0,0,0,0.15)'"
onmouseout="this.style.transform='translateY(0px)'; this.style.boxShadow='0 2px 4px rgba(0,0,0,0.1)'">
<div style="text-align: center; margin-bottom: 15px;">
<img src="{app.thumbnail}"
style="width: 100%; height: 120px; object-fit: cover; border-radius: 8px; background: #f5f5f5;"
alt="App thumbnail"/>
</div>
<h3 style="color: {ACCENT_COLOR}; margin: 0 0 10px 0; font-size: 18px; text-align: center;">
{app.name}
</h3>
<p style="color: #666; margin: 0 0 15px 0; font-size: 14px; line-height: 1.4; flex-grow: 1; overflow: hidden;">
{app.description[:120]}{"..." if len(app.description) > 120 else ""}
</p>
<div style="text-align: center; margin-top: auto;">
<a href="{app.url()}"
style="
background: {ACCENT_COLOR};
color: white;
padding: 8px 16px;
text-decoration: none;
border-radius: 6px;
font-weight: 500;
transition: background-color 0.2s;
"
onmouseover="this.style.backgroundColor='{ACCENT_FILL_HOVER}'"
onmouseout="this.style.backgroundColor='{ACCENT_COLOR}'">
Open App →
</a>
</div>
</div>
"""
return pn.pane.HTML(card_html, width=300, height=320)
def _create_pagination_controls(self, total_apps: int) -> pn.Row:
"""Create pagination controls"""
# Safely get parameter values with defaults
current_page = max(1, getattr(self, "current_page", 1) or 1)
apps_per_page = max(1, getattr(self, "apps_per_page", APPS_PER_PAGE) or APPS_PER_PAGE)
total_pages = math.ceil(total_apps / apps_per_page)
if total_pages <= 1:
return pn.Row()
controls = []
# Previous button
if current_page > 1:
prev_btn = pn.widgets.Button(
name="← Previous",
button_style="outline",
)
prev_btn.on_click(lambda event: self._go_to_page(current_page - 1))
controls.append(prev_btn)
# Page numbers
start_page = max(1, current_page - 2)
end_page = min(total_pages, current_page + 2)
for page_num in range(start_page, end_page + 1):
if page_num == current_page:
# Current page (highlighted)
page_btn = pn.widgets.Button(
name=str(page_num), button_type="primary", margin=(5, 5), width=40
)
else:
# Other pages
page_btn = pn.widgets.Button(
name=str(page_num), button_style="outline", margin=(5, 5), width=40
)
page_btn.on_click(lambda event, p=page_num: self._go_to_page(p))
controls.append(page_btn)
# Next button
if current_page < total_pages:
next_btn = pn.widgets.Button(name="Next →", button_style="outline", margin=(5, 10))
next_btn.on_click(lambda event: self._go_to_page(current_page + 1))
controls.append(next_btn)
return pn.Row(*controls)
def _go_to_page(self, page_num: int):
"""Navigate to a specific page"""
self.current_page = page_num # Reactivity will handle the update automatically
@param.depends("search_term", "current_page", watch=True)
def _update_display(self):
"""Update the apps display based on current filters and page"""
# Get filtered apps
filtered_apps = self._get_filtered_apps()
# Get paginated apps
paginated_apps = self._get_paginated_apps(filtered_apps)
# Clear current display
self._apps_container.clear()
self._pagination.clear()
if not paginated_apps:
# No apps found
no_apps_msg = pn.pane.HTML(
"<div style='text-align: center; padding: 40px; color: #666;'>"
"<h3>No apps found</h3>"
"<p>Try adjusting your search terms.</p>"
"</div>",
sizing_mode="stretch_width",
)
self._apps_container.append(no_apps_msg)
else:
# Create grid of app cards
self._apps_container[:] = [self._create_app_card(app) for app in paginated_apps]
# Update pagination
pagination_controls = self._create_pagination_controls(len(filtered_apps))
if len(pagination_controls):
self._pagination[:] = pagination_controls
def __panel__(self):
"""Return the panel for notebook display"""
return self._layout
@classmethod
def create_app(cls, **params):
"""Create a servable app instance"""
instance = cls(**params)
return pn.template.FastListTemplate(
title="Office Hours",
sidebar_width=0, # No sidebar needed
header_background=ACCENT_COLOR,
main_layout=None,
main=instance,
favicon=r"assets\favicon-01.png",
)
# Serve the app
if __name__ == "__main__":
# Run with: python index.py
AppGallery.create_app().show(
port=5030, autoreload=True, open=True, static_dirs={"assets": "./assets"}
)
elif pn.state.served:
# Run with: panel serve *.py --index index.py --dev --show
AppGallery.create_app().servable()
/tasks/setup.py
import sys
import threading
import time
import numpy as np
import panel as pn
running = True
import random
def random_dict():
keys = ["x", "y", "z", "a", "b", "c", "d", "e", "f", "g"]
# Generate a list of random integers corresponding to the number of keys
random_values = [random.randint(1, 50) for _ in keys]
return dict(zip(keys, random_values))
def worker():
"""
Worker function to read data from network object and update the data in the Panel state cache.
Parameters:
None
Returns:
None
"""
while running:
time.sleep(0.001)
pn.state.cache["data"] = random_dict()
def session_created(session_context):
"""
Function to handle the creation of a new session.
Parameters:
- session_context (object): The context of the session being created.
Returns:
None
"""
print(f"Created a session running at the {session_context.request.uri} endpoint")
thread = threading.Thread(target=worker)
thread.start()
pn.state.on_session_created(session_created)
def session_destroyed(session_context):
"""
Stop the object and exit the program when a session is destroyed.
Parameters:
- session_context (object): The context of the session that was destroyed.
Returns:
None
"""
print(f"Closed a session running at the {session_context.request.uri} endpoint")
sys.exit()
pn.state.on_session_destroyed(session_destroyed)
Running the MRE using the following command and navigating between the apps via the index quickly breaks down.
python -m panel serve .\*.py --glob --setup .\tasks\setup.py --index index.py
Any help would be greatly appreciated.