← Back to list

panel-architecture
by AllenNeuralDynamics
Panel app for visualizing analysis results in AIND Analysis Framework
⭐ 0🍴 0📅 Jan 22, 2026
SKILL.md
name: panel-architecture description: Best practices for building Panel visualization apps using param.Parameterized classes, reactive binding with pn.bind(), and URL state sync.
Panel Application Architecture
Core Patterns
1. DataHolder for Reactive State
import param
import pandas as pd
class DataHolder(param.Parameterized):
"""Central state container that components watch."""
selected_id = param.String(default="")
filtered_df = param.DataFrame()
is_loaded = param.Boolean(default=False)
2. Reactive Binding with pn.bind()
# Function called when parameter changes
display = pn.bind(
self.render_content,
record_id=self.data_holder.param.selected_id,
df=self.data_holder.param.filtered_df,
)
pn.Column(display)
3. Component Pattern
class MyComponent:
def __init__(self, data_holder, config):
self.data_holder = data_holder
self.config = config
def create(self):
"""Return Panel viewable. Called once or reactively."""
return pn.bind(self._render, df=self.data_holder.param.filtered_df)
def _render(self, df):
# Create and return widget/pane
...
URL State Synchronization
When to Use Each Pattern
| Scenario | Use | Why |
|---|---|---|
| Widget created once | location.sync() | Simple, bidirectional |
| Widget in reactive render | One-way sync | Avoids race conditions |
| Multiple syncs updating URL | One-way sync | Prevents widget reversion |
Native Sync (for static widgets)
def _sync_url_state(self):
location = pn.state.location
location.sync(self.my_widget, {'value': 'param_name'})
One-Way Sync (for reactive widgets)
from urllib.parse import parse_qs
def get_url_param(name, default=None):
query = pn.state.location.search or ""
params = parse_qs(query.lstrip("?"))
return params.get(name, [default])[0]
def update_url_param(name, value):
pn.state.location.update_query(**{name: value})
# Usage
class MyComponent:
def __init__(self):
self._initial = get_url_param("my_param") # Read once
def create(self):
widget = pn.widgets.Select(value=self._initial, ...)
widget.param.watch(
lambda e: update_url_param("my_param", e.new), "value"
)
return widget
Auto-Load on Page Load
def main_layout(self):
# ... create layout ...
def _autoload():
if get_url_param("project"):
self.load_data()
pn.state.onload(_autoload)
return template
Layout Patterns
Tabulator for DataFrames
table = pn.widgets.Tabulator(
df,
selectable=1,
frozen_columns=["id"],
header_filters=True,
height=400,
sizing_mode="stretch_width",
)
def on_selection(event):
if event.new:
data_holder.selected_id = str(df.iloc[event.new[0]]["_id"])
table.param.watch(on_selection, "selection")
Bokeh Scatter with Hover Image
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, HoverTool
source = ColumnDataSource(df)
p = figure(tools="pan,wheel_zoom,reset,tap")
scatter = p.scatter(x="x", y="y", source=source)
tooltips = '<img src="@image_url{safe}" width="400">'
p.add_tools(HoverTool(tooltips=tooltips, renderers=[scatter]))
Best Practices
- Use
pn.bind()over callbacks - cleaner, more efficient - Throttle expensive updates:
slider.param.value_throttled - Update data sources, don't recreate widgets
- Cache data loading:
@pn.cache(ttl=3600)
Project Structure
code/
├── app.py # Entry point
├── config/ # Configuration (models.py, projects.py)
├── data/ # Data loaders with caching
├── core/ # State management (DataHolder)
├── components/ # UI components
└── utils/ # Helpers (url_state.py)
Running
# Development
panel serve code/app.py --dev --show
# Production
panel serve code/app.py --allow-websocket-origin=* --port 7860
Score
Total Score
60/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon