holoviz-mcp 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- holoviz_mcp/__init__.py +18 -0
- holoviz_mcp/apps/__init__.py +1 -0
- holoviz_mcp/apps/configuration_viewer.py +116 -0
- holoviz_mcp/apps/holoviz_get_best_practices.py +173 -0
- holoviz_mcp/apps/holoviz_search.py +319 -0
- holoviz_mcp/apps/hvplot_get_docstring.py +255 -0
- holoviz_mcp/apps/hvplot_get_signature.py +252 -0
- holoviz_mcp/apps/hvplot_list_plot_types.py +83 -0
- holoviz_mcp/apps/panel_get_component.py +496 -0
- holoviz_mcp/apps/panel_get_component_parameters.py +467 -0
- holoviz_mcp/apps/panel_list_components.py +311 -0
- holoviz_mcp/apps/panel_list_packages.py +71 -0
- holoviz_mcp/apps/panel_search_components.py +312 -0
- holoviz_mcp/cli.py +75 -0
- holoviz_mcp/client.py +94 -0
- holoviz_mcp/config/__init__.py +29 -0
- holoviz_mcp/config/config.yaml +178 -0
- holoviz_mcp/config/loader.py +316 -0
- holoviz_mcp/config/models.py +208 -0
- holoviz_mcp/config/resources/best-practices/holoviews.md +423 -0
- holoviz_mcp/config/resources/best-practices/hvplot.md +465 -0
- holoviz_mcp/config/resources/best-practices/panel-material-ui.md +318 -0
- holoviz_mcp/config/resources/best-practices/panel.md +562 -0
- holoviz_mcp/config/schema.json +228 -0
- holoviz_mcp/holoviz_mcp/__init__.py +1 -0
- holoviz_mcp/holoviz_mcp/data.py +970 -0
- holoviz_mcp/holoviz_mcp/models.py +21 -0
- holoviz_mcp/holoviz_mcp/pages_design.md +407 -0
- holoviz_mcp/holoviz_mcp/server.py +220 -0
- holoviz_mcp/hvplot_mcp/__init__.py +1 -0
- holoviz_mcp/hvplot_mcp/server.py +146 -0
- holoviz_mcp/panel_mcp/__init__.py +17 -0
- holoviz_mcp/panel_mcp/data.py +319 -0
- holoviz_mcp/panel_mcp/models.py +124 -0
- holoviz_mcp/panel_mcp/server.py +443 -0
- holoviz_mcp/py.typed +0 -0
- holoviz_mcp/serve.py +36 -0
- holoviz_mcp/server.py +86 -0
- holoviz_mcp/shared/__init__.py +1 -0
- holoviz_mcp/shared/extract_tools.py +74 -0
- holoviz_mcp/thumbnails/configuration_viewer.png +0 -0
- holoviz_mcp/thumbnails/holoviz_get_best_practices.png +0 -0
- holoviz_mcp/thumbnails/holoviz_search.png +0 -0
- holoviz_mcp/thumbnails/hvplot_get_docstring.png +0 -0
- holoviz_mcp/thumbnails/hvplot_get_signature.png +0 -0
- holoviz_mcp/thumbnails/hvplot_list_plot_types.png +0 -0
- holoviz_mcp/thumbnails/panel_get_component.png +0 -0
- holoviz_mcp/thumbnails/panel_get_component_parameters.png +0 -0
- holoviz_mcp/thumbnails/panel_list_components.png +0 -0
- holoviz_mcp/thumbnails/panel_list_packages.png +0 -0
- holoviz_mcp/thumbnails/panel_search_components.png +0 -0
- holoviz_mcp-0.4.0.dist-info/METADATA +216 -0
- holoviz_mcp-0.4.0.dist-info/RECORD +56 -0
- holoviz_mcp-0.4.0.dist-info/WHEEL +4 -0
- holoviz_mcp-0.4.0.dist-info/entry_points.txt +2 -0
- holoviz_mcp-0.4.0.dist-info/licenses/LICENSE.txt +30 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: panel-development
|
|
3
|
+
description: Build interactive web applications and dashboards with HoloViz Panel. Create reactive, component-based UIs with widgets, layouts, templates, and real-time updates. Use when developing data apps, monitoring dashboards, data exploration tools, or any interactive Python web application. Supports file uploads, streaming data, multi-page apps, and integration with HoloViews, hvPlot, and the rest of the HoloViz and PyData ecosystem.
|
|
4
|
+
metadata:
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
author: holoviz
|
|
7
|
+
category: web-development
|
|
8
|
+
difficulty: intermediate
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Panel Development Skill
|
|
12
|
+
|
|
13
|
+
This document provides best practices for developing dashboards and data apps with HoloViz Panel in Python .py files.
|
|
14
|
+
|
|
15
|
+
Please develop as an **Expert Python Developer** developing advanced data-driven, analytics and testable dashboards and applications would do. Keep the code short, concise, documented, testable and professional.
|
|
16
|
+
|
|
17
|
+
## Dependencies
|
|
18
|
+
|
|
19
|
+
Core dependencies provided with the `panel` Python package:
|
|
20
|
+
|
|
21
|
+
- **panel**: Core application framework
|
|
22
|
+
- **param**: A declarative approach to creating classes with typed, validated, and documented parameters. Fundamental to Panel's reactive programming model.
|
|
23
|
+
|
|
24
|
+
Optional panel-extensions:
|
|
25
|
+
|
|
26
|
+
- **panel-material-ui**: Modern Material UI components. To replace the panel native widgets within the next two years.
|
|
27
|
+
- **panel-graphic-walker**: Modern Tableau like interface. Can offload computations to the server and thus scale to large datasets.
|
|
28
|
+
|
|
29
|
+
Optional dependencies from the HoloViz Ecosystem:
|
|
30
|
+
|
|
31
|
+
- **colorcet**: Perceptually uniform colormaps collection. Best for: scientific visualization requiring accurate color representation, avoiding rainbow colormaps, accessible color schemes. Integrates with hvPlot, HoloViews, Matplotlib, Bokeh.
|
|
32
|
+
- **datashader**: Renders large datasets (millions+ points) into images for visualization. Best for: big data visualization, geospatial datasets, scatter plots with millions of points, heatmaps of dense data. Requires hvPlot or HoloViews as frontend.
|
|
33
|
+
- **geoviews**: Geographic data visualization with map projections and tile sources. Best for: geographic/geospatial plots, map-based dashboards, when you need coordinate systems and projections. Built on HoloViews, works seamlessly with hvPlot.
|
|
34
|
+
- **holoviews**: Declarative data visualization library with composable elements. Best for: complex multi-layered plots, advanced interactivity (linked brushing, selection), when you need fine control over plot composition, scientific visualizations. More powerful but steeper learning curve than hvPlot.
|
|
35
|
+
- **holoviz-mcp**: Model Context Protocol server for HoloViz ecosystem. Provides access to detailed documentation, component search and best practices.
|
|
36
|
+
- **hvplot**: High-level plotting API with Pandas `.plot()`-like syntax. Best for: quick exploratory visualizations, interactive plots from DataFrames/Xarray, when you want interactivity without verbose code. Built on HoloViews.
|
|
37
|
+
- **hvsampledata**: Shared datasets for the HoloViz projects.
|
|
38
|
+
|
|
39
|
+
Optional dependencies from the wider PyData Ecosystem:
|
|
40
|
+
|
|
41
|
+
- **altair**: Declarative, grammar-of-graphics visualization library. Best for: statistical visualizations, interactive exploratory charts, when you need Vega-Lite's extensive chart gallery. Works well with Pandas/Polars DataFrames.
|
|
42
|
+
- **dask**: Parallel computing library for scaling Pandas DataFrames beyond memory. Best for: processing datasets larger than RAM, parallel computation across multiple cores/machines, lazy evaluation workflows.
|
|
43
|
+
- **duckdb**: High-performance analytical SQL database. Best for: fast SQL queries on DataFrames, aggregations on large datasets, when you need SQL interface, OLAP-style analytics. Much faster than Pandas for analytical queries.
|
|
44
|
+
- **matplotlib**: Low-level, highly customizable plotting library. Best for: publication-quality static plots, fine-grained control over every aspect of visualization, scientific plots, when you need pixel-perfect control.
|
|
45
|
+
- **pandas**: Industry-standard DataFrame library for tabular data. Best for: data cleaning, transformation, time series analysis, datasets that fit in memory. The default choice for most data work.
|
|
46
|
+
- **Plotly**: Interactive, publication-quality visualization library. Best for: 3D plots, complex interactive charts, animations, when you need hover tooltips and interactivity. Works well with Dash and Panel.
|
|
47
|
+
- **polars**: Modern, fast DataFrame library written in Rust. Best for: high-performance data processing, datasets that fit in memory but need speed, when you need lazy evaluation, better memory efficiency than Pandas.
|
|
48
|
+
- **xarray**: N-dimensional labeled arrays and datasets. Best for: multidimensional scientific data (climate, satellite imagery), data with multiple dimensions and coordinates, NetCDF/HDF5 files, geospatial raster data.
|
|
49
|
+
- **watchfiles**: Enables high performance file watching and autoreload for the panel server.
|
|
50
|
+
|
|
51
|
+
## Common Use Cases
|
|
52
|
+
|
|
53
|
+
1. **Real-time Monitoring Dashboards**: Live metrics and KPI displays
|
|
54
|
+
2. **Data Exploration Tools**: Interactive data analysis applications
|
|
55
|
+
3. **Configuration Interfaces**: Complex multi-step configuration UIs
|
|
56
|
+
4. **Data Input Applications**: Validated form-based data collection
|
|
57
|
+
5. **Report Viewers**: Interactive report generation and browsing
|
|
58
|
+
6. **Administrative Interfaces**: Internal tools for data management
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install panel watchfiles hvplot hvsampledata
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
For development in .py files DO always include watchfiles for hotreload.
|
|
67
|
+
|
|
68
|
+
## Best Practice Hello World App
|
|
69
|
+
|
|
70
|
+
Let's describe our best practices via a basic Hello World App:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# DO import panel as pn
|
|
74
|
+
import panel as pn
|
|
75
|
+
import param
|
|
76
|
+
|
|
77
|
+
# DO always run pn.extension
|
|
78
|
+
# DO remember to add any imports needed by panes, e.g. pn.extension("tabulator", "plotly", ...)
|
|
79
|
+
# DON'T add "bokeh" as an extension. It is not needed.
|
|
80
|
+
# Do use throttled=True when using slider unless you have a specific reason not to
|
|
81
|
+
pn.extension(throttled=True)
|
|
82
|
+
|
|
83
|
+
# DO organize functions to extract data separately as your app grows. Eventually in a separate data.py file.
|
|
84
|
+
# DO use caching to speed up the app, e.g. for expensive data loading or processing that would return the same result given same input arguments.
|
|
85
|
+
# DO add a ttl (time to live argument) for expensive data loading that changes over time
|
|
86
|
+
@pn.cache(max_items=3)
|
|
87
|
+
def extract(n=5):
|
|
88
|
+
return "Hello World" + "⭐" * n
|
|
89
|
+
|
|
90
|
+
text = extract()
|
|
91
|
+
text_len = len(text)
|
|
92
|
+
|
|
93
|
+
# DO organize functions to transform data separately as your app grows. Eventually in a separate transformations.py file
|
|
94
|
+
# DO add caching to speed up expensive data transformations
|
|
95
|
+
@pn.cache(max_items=3)
|
|
96
|
+
def transform(data: str, count: int=5)->str:
|
|
97
|
+
count = min(count, len(data))
|
|
98
|
+
return data[:count]
|
|
99
|
+
|
|
100
|
+
# DO organize functions to create plots separately as your app grows. Eventually in a separate plots.py file.
|
|
101
|
+
# DO organize custom components and views separately as your app grows. Eventually in separate components.py or views.py file(s).
|
|
102
|
+
# DO use param.Parameterized, pn.viewable.Viewer or similar approach to create new components and apps with state and reactivity
|
|
103
|
+
class HelloWorld(pn.viewable.Viewer):
|
|
104
|
+
# DO define parameters to hold state and drive the reactivity
|
|
105
|
+
characters = param.Integer(default=text_len, bounds=(0, text_len), doc="Number of characters to display")
|
|
106
|
+
|
|
107
|
+
def __init__(self, **params):
|
|
108
|
+
super().__init__(**params)
|
|
109
|
+
|
|
110
|
+
# DO use sizing_mode="stretch_width" for components unless "fixed" or other sizing_mode is specifically needed
|
|
111
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
112
|
+
# DO create widgets using `.from_param` method
|
|
113
|
+
self._characters_input = pn.widgets.IntSlider.from_param(self.param.characters, margin=(10,20))
|
|
114
|
+
|
|
115
|
+
# DO Collect input widgets into horizontal, columnar layout unless other layout is specifically needed
|
|
116
|
+
self._inputs = pn.Column(self._characters_input, max_width=300)
|
|
117
|
+
|
|
118
|
+
# CRITICAL: Create panes ONCE with reactive content
|
|
119
|
+
# DON'T recreate panes and layouts in @param.depends methods - causes flickering!
|
|
120
|
+
# DO bind reactive methods/functions to panes for smooth updates
|
|
121
|
+
self._output_pane = pn.pane.Markdown(
|
|
122
|
+
self.model, # Reactive method reference
|
|
123
|
+
sizing_mode="stretch_width"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# DO collect output components into some layout like Column, Row, FlexBox or Grid depending on use case
|
|
127
|
+
self._outputs = pn.Column(self._output_pane)
|
|
128
|
+
|
|
129
|
+
# DO collect all of your components into a combined layout useful for displaying in notebooks etc.
|
|
130
|
+
self._panel = pn.Row(self._inputs, self._outputs)
|
|
131
|
+
|
|
132
|
+
# DO use caching to speed up bound methods that are expensive to compute or load data and return the same result for a given state of the class.
|
|
133
|
+
# DO add a ttl (time to live argument) for expensive data loading that changes over time.
|
|
134
|
+
@pn.cache(max_items=3)
|
|
135
|
+
# DO prefer .depends over .bind over .rx for reactivity methods on Parameterized classes as it can be typed and documented
|
|
136
|
+
# DON'T use `watch=True` or `.watch(...)` methods to update UI components directly.
|
|
137
|
+
# DO use `watch=True` or `.watch(...)` for updating the state parameters or triggering side effects like saving files or sending email.
|
|
138
|
+
@param.depends("characters")
|
|
139
|
+
def model(self):
|
|
140
|
+
# CRITICAL: Return ONLY the content, NOT the layout/pane
|
|
141
|
+
# The pane was created once in __init__, this just updates its content
|
|
142
|
+
return transform(text, self.characters)
|
|
143
|
+
|
|
144
|
+
# DO use `watch=True` or `.watch(...)` for updating the state parameters or triggering side effects like saving files or sending email.
|
|
145
|
+
@param.depends("characters", watch=True)
|
|
146
|
+
def _inform_user(self):
|
|
147
|
+
print(f"User selected to show {self.characters} characters.")
|
|
148
|
+
|
|
149
|
+
# DO provide a method for displaying the component in a notebook setting, i.e. without using a Template or other element that cannot be displayed in a notebook setting.
|
|
150
|
+
def __panel__(self):
|
|
151
|
+
return self._panel
|
|
152
|
+
|
|
153
|
+
# DO provide a method to create a .servable app
|
|
154
|
+
@classmethod
|
|
155
|
+
def create_app(cls, **params):
|
|
156
|
+
instance = cls(**params)
|
|
157
|
+
# DO use a Template or similar page layout for served apps
|
|
158
|
+
template = pn.template.FastListTemplate(
|
|
159
|
+
# DO provide a title for the app
|
|
160
|
+
title="Hello World App",
|
|
161
|
+
# DO provide optional image, optional app description, optional navigation menu, input widgets, optional documentation and optional links in the sidebar
|
|
162
|
+
# DO provide as list of components or a list of single horizontal layout like Column as the sidebar by default is 300 px wide
|
|
163
|
+
sidebar=[instance._inputs],
|
|
164
|
+
# DO provide a list of layouts and output components in the main area of the app.
|
|
165
|
+
# DO use Grid or FlexBox layouts for complex dashboard layouts instead of combination Rows and Columns.
|
|
166
|
+
main=[instance._outputs],
|
|
167
|
+
)
|
|
168
|
+
return template
|
|
169
|
+
|
|
170
|
+
# DON'T provide a `if __name__ == "__main__":` method to serve the app with `python`
|
|
171
|
+
# DO provide a method to serve the app with `panel serve`
|
|
172
|
+
if pn.state.served:
|
|
173
|
+
# Mark components to be displayed in the app with .servable()
|
|
174
|
+
HelloWorld.create_app().servable()
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
DO serve the app with
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
panel serve path_to_this_file.py --show --dev
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
DON'T serve with `python path_to_this_file.py`.
|
|
184
|
+
|
|
185
|
+
## Best Practice Hello World Tests
|
|
186
|
+
|
|
187
|
+
With panel you can easily create tests to test user behaviour without having to write client side tests.
|
|
188
|
+
|
|
189
|
+
DO always create separate tests in the `tests` folder:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
# DO put tests in a separate test file.
|
|
193
|
+
# DO always test that the reactivity works as expected
|
|
194
|
+
def test_characters_reactivity():
|
|
195
|
+
"""
|
|
196
|
+
Test characters reactivity.
|
|
197
|
+
"""
|
|
198
|
+
# DO test the default values of bound
|
|
199
|
+
hello_world = HelloWorld()
|
|
200
|
+
# DO test the reactivity of bound methods when parameters change
|
|
201
|
+
assert hello_world.model() == text[:hello_world.characters]
|
|
202
|
+
hello_world.characters = 5
|
|
203
|
+
assert hello_world.model() == text[:5]
|
|
204
|
+
hello_world.characters = 3
|
|
205
|
+
assert hello_world.model() == text[:3]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
DO run the tests with:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pytest tests/path/to/test_file.py
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
DO fix any errors identified.
|
|
215
|
+
|
|
216
|
+
## Key Patterns
|
|
217
|
+
|
|
218
|
+
### Parameter-Driven Architecture
|
|
219
|
+
|
|
220
|
+
- DO use `param.Parameterized` or `pn.viewable.Viewer` classes to organize and manage state
|
|
221
|
+
- DO create widgets with `.from_param()` method
|
|
222
|
+
- DO use `@param.depends()` for reactive methods
|
|
223
|
+
- DO use `@param.depends(..., watch=True)` to update parameter/ state values and for side-effects like sending an email.
|
|
224
|
+
- DO group related parameters in separate `Parameterized` or `Viewable` classes
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# ❌ AVOID: Updating panes and other components directly. This makes it hard to reason about application flow and state
|
|
229
|
+
@param.depends('value', watch=True)
|
|
230
|
+
def update_plot(self):
|
|
231
|
+
self.output_pane.object = transform(text, self.characters)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Create Static Layout with Reactive Content (CRITICAL)
|
|
235
|
+
|
|
236
|
+
**The Golden Rule: Create layout structure ONCE, update content REACTIVELY**
|
|
237
|
+
|
|
238
|
+
This pattern eliminates flickering and creates professional Panel applications:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# ✅ CORRECT: Create panes ONCE in __init__, bind reactive content
|
|
242
|
+
class Dashboard(pn.viewable.Viewer):
|
|
243
|
+
filter_value = param.String(default="all")
|
|
244
|
+
|
|
245
|
+
chart = param.Parameter()
|
|
246
|
+
|
|
247
|
+
def __init__(self, **params):
|
|
248
|
+
super().__init__(**params)
|
|
249
|
+
|
|
250
|
+
# 1. Create static panes with reactive content
|
|
251
|
+
self._summary_pane = pn.pane.Markdown(self._summary_text)
|
|
252
|
+
self._chart_pane = pn.pane.HoloViews(self.param.chart)
|
|
253
|
+
|
|
254
|
+
# 2. Create static layout structure
|
|
255
|
+
self._layout = pn.Column(
|
|
256
|
+
"# Dashboard", # Static title
|
|
257
|
+
self._summary_pane, # Reactive content
|
|
258
|
+
self._chart_pane, # Reactive content
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# ✅ Good: Reactive content method
|
|
262
|
+
# Will be run multiple times when filter_value updates if multiple panes or reactive functions depend on the _summary_text method
|
|
263
|
+
@param.depends("filter_value")
|
|
264
|
+
def _summary_text(self):
|
|
265
|
+
# Returns string content only, NOT a pane
|
|
266
|
+
return f"**Count**: {len(self._get_data())}"
|
|
267
|
+
|
|
268
|
+
# ✅ Good: Reactive update of chart parameter
|
|
269
|
+
# Will be run only one time when filter_value updates - even if multiple panes or reactive functions depend on the chart value
|
|
270
|
+
@param.depends("filter_value", watch=True, on_init=True)
|
|
271
|
+
def _update_chart(self):
|
|
272
|
+
# updates the chart object only, NOT a pane
|
|
273
|
+
self.chart = self._get_data().hvplot.bar()
|
|
274
|
+
|
|
275
|
+
def __panel__(self):
|
|
276
|
+
return self._layout
|
|
277
|
+
|
|
278
|
+
# ❌ WRONG: Recreating layout in @param.depends - causes flickering!
|
|
279
|
+
class BadDashboard(pn.viewable.Viewer):
|
|
280
|
+
filter_value = param.String(default="all")
|
|
281
|
+
|
|
282
|
+
@param.depends("filter_value")
|
|
283
|
+
def view(self):
|
|
284
|
+
# DON'T recreate panes/layouts on every parameter change!
|
|
285
|
+
return pn.Column(
|
|
286
|
+
"# Dashboard",
|
|
287
|
+
pn.pane.Markdown(f"**Count**: {len(self._get_data())}"),
|
|
288
|
+
pn.pane.HoloViews(self._get_data().hvplot.bar()),
|
|
289
|
+
)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Why This Matters:**
|
|
293
|
+
|
|
294
|
+
- ✅ Smooth updates without layout reconstruction
|
|
295
|
+
- ✅ No flickering - seamless transitions
|
|
296
|
+
- ✅ Better performance - avoids unnecessary DOM updates
|
|
297
|
+
- ✅ Professional UX
|
|
298
|
+
|
|
299
|
+
**Key Rules:**
|
|
300
|
+
|
|
301
|
+
1. DO create main layout structure and panes ONCE in `__init__`
|
|
302
|
+
2. DO bind panes to reactive methods or parameters (DON'T recreate them)
|
|
303
|
+
3. Reactive methods should return CONTENT only (strings, plots, dataframes), NOT panes/layouts
|
|
304
|
+
4. Use `@param.depends` for reactive methods that update pane content
|
|
305
|
+
|
|
306
|
+
### Widgets
|
|
307
|
+
|
|
308
|
+
Use
|
|
309
|
+
|
|
310
|
+
- `pn.widgets.IntSlider`, `pn.widgets.Select`, `pn.widgets.DateRangeSlider` and other widgets for input
|
|
311
|
+
- `pn.widgets.Tabulator` to display tabular data like DataFrames
|
|
312
|
+
|
|
313
|
+
### Panes
|
|
314
|
+
|
|
315
|
+
Use panes to display data:
|
|
316
|
+
|
|
317
|
+
- `pn.pane.Markdown` and `pn.pane.HTML` to display strings
|
|
318
|
+
- `pn.pane.HoloViews`, `pn.pane.Plotly`, `pn.pane.Matplotlib` or `pn.pane.ECharts` to display plots
|
|
319
|
+
|
|
320
|
+
### Layouts
|
|
321
|
+
|
|
322
|
+
Use layouts to layout components:
|
|
323
|
+
|
|
324
|
+
- DO use `pn.Column`, `pn.Row`, `pn.Tabs`, `pn.Accordion` for layouts
|
|
325
|
+
|
|
326
|
+
### Templates
|
|
327
|
+
|
|
328
|
+
- DO use `pn.template.FastListTemplate` or other templates for served apps:
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
template = pn.template.FastListTemplate(
|
|
332
|
+
title="Hello World App",
|
|
333
|
+
sidebar=[instance._inputs],
|
|
334
|
+
main=[instance._outputs],
|
|
335
|
+
main_layout=None,
|
|
336
|
+
)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
- In the `sidebar`, DO use the order: 1) optional logo, 2) description, 3) input widgets, 4) documentation
|
|
340
|
+
- Do set `main_layout=None` for a modern styling.
|
|
341
|
+
|
|
342
|
+
### Responsive Design
|
|
343
|
+
|
|
344
|
+
- DO use `sizing_mode="stretch_width"` by default:
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
with pn.config.set(sizing_mode="stretch_width"):
|
|
348
|
+
character_input = pn.widgets...
|
|
349
|
+
output_pane = pn.pane....
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
- DO use `FlexBox`, `GridSpec` or `GridBox` for complex, responsive grid layouts
|
|
353
|
+
- DO set appropriate `min_width`, `min_height`, `max_width` and `max_height` to prevent layout collapse
|
|
354
|
+
|
|
355
|
+
### Extensions
|
|
356
|
+
|
|
357
|
+
DO remember to add extensions like "tabulator", "plotly" etc. to `pn.extension` to make sure their Javascript is loaded:
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
# ✅ Good
|
|
361
|
+
pn.extension("tabulator", "plotly")
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
DON'T add "bokeh". It's already loaded:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
# ❌ Bad
|
|
368
|
+
pn.extension("bokeh")
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Servable()
|
|
372
|
+
|
|
373
|
+
DO make the main component `.servable()` to include it in the served app and use `pn.state.served` to run the main method when the app is panel serve'd.
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
# ✅ Correct:
|
|
377
|
+
if pn.state.served:
|
|
378
|
+
main().servable()
|
|
379
|
+
|
|
380
|
+
# ❌ Incorrect:
|
|
381
|
+
if __name__ == "__main__":
|
|
382
|
+
main().servable()
|
|
383
|
+
|
|
384
|
+
# ❌ Don't: Works, but not how we want to serve the app:
|
|
385
|
+
if __name__ == "__main__":
|
|
386
|
+
main().show()
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Performance Optimization
|
|
390
|
+
|
|
391
|
+
- **Defer load**: Defer load to after the app is shown to the user: `pn.extension(defer_load=True, loading_indicator=True, ...)`
|
|
392
|
+
- **Lazy-load components** using Tabs or Accordion for heavy content
|
|
393
|
+
- **Use caching** with `@pn.cache` decorator for expensive computations
|
|
394
|
+
- **Use async/await**: Implement asynchronous patterns for I/O operations
|
|
395
|
+
- **Use faster frameworks**: Replace slower Pandas with faster Polars or DuckDB
|
|
396
|
+
- **Offload to threads**: Consider using threading for CPU-intensive tasks
|
|
397
|
+
- **Offload to external processes**: Consider offloading heavy computations to external processes like databases, scheduled (airflow) jobs, REST apis etc.
|
|
398
|
+
- **Profile callbacks** with `@pn.io.profiler` to identify bottlenecks
|
|
399
|
+
|
|
400
|
+
If you experience memory issues, make sure to:
|
|
401
|
+
|
|
402
|
+
- **Limit history**: Cap data history sizes in streaming applications
|
|
403
|
+
- **Clear caches**: Periodically call `pn.state.clear_caches()`
|
|
404
|
+
- **Restart periodically**: Schedule application restarts for long-running production apps
|
|
405
|
+
- **Profile memory**: Use memory profilers (memory_profiler, tracemalloc) to identify leaks
|
|
406
|
+
|
|
407
|
+
### Code Organization
|
|
408
|
+
|
|
409
|
+
- **Separate concerns**: Keep UI code separate from business logic using Param classes
|
|
410
|
+
- **Create reusable components**: Extract common patterns into functions or classes
|
|
411
|
+
- **Use templates** for consistent application structure across pages
|
|
412
|
+
- **Organize modules**: Group related components and utilities
|
|
413
|
+
- **Document parameters**: Add clear docstrings to Parameterized classes
|
|
414
|
+
|
|
415
|
+
## Workflows
|
|
416
|
+
|
|
417
|
+
### Lookup additional information
|
|
418
|
+
|
|
419
|
+
- If the HoloViz MCP server is available DO use the HoloViz MCP server to access relevant documentation including how-to guides, component reference guides, and detailed component docstrings and parameter information.
|
|
420
|
+
- If the HoloViz MCP server is not available, DO search the web. For example searching the Panel website for `Tabulator` related information via [https://panel.holoviz.org/search.html?q=Tabulator](https://panel.holoviz.org/search.html?q=Tabulator) url.
|
|
421
|
+
|
|
422
|
+
### Test the app with pytest
|
|
423
|
+
|
|
424
|
+
DO add tests to the `tests` folder and run them with `pytest tests/path/to/test_file.py`.
|
|
425
|
+
|
|
426
|
+
- DO structure your code with Parameterized components, so that reactivity and user interactions can be tested easily.
|
|
427
|
+
- DO separate UI logic from business logic to enable unit testing
|
|
428
|
+
- DO separate data extraction, data transformation, plots generation, custom components and views, styles etc. to enable unit testing as your app grows
|
|
429
|
+
- DO fix any test errors and rerun the tests
|
|
430
|
+
- DO run the tests and fix errors before serving the app and asking the user to run manual tests
|
|
431
|
+
|
|
432
|
+
### Serve the App with panel serve
|
|
433
|
+
|
|
434
|
+
DO always start and keep running a development server `panel serve path_to_file.py --dev --show` with hot reload while developing!
|
|
435
|
+
|
|
436
|
+
- Due to `--show` flag, a browser tab will automatically open showing your app.
|
|
437
|
+
- Due to `--dev` flag, the panel server and app will automatically reload if you change the code.
|
|
438
|
+
- The app will be served at http://localhost:5006/.
|
|
439
|
+
- DO make sure the correct virtual environment is activated before serving the app.
|
|
440
|
+
- DO fix any errors that show up in the terminal. Consider adding new tests to ensure they don't happen again.
|
|
441
|
+
- DON'T stop or restart the server after changing the code. The app will automatically reload.
|
|
442
|
+
- If you see 'Cannot start Bokeh server, port 5006 is already in use' in the terminal, DO serve the app on another port with `--port {port-number}` flag.
|
|
443
|
+
- DO remind the user to test the application on multiple screen sizes (desktop, tablet, mobile)
|
|
444
|
+
- DON'T use legacy `--autoreload` flag
|
|
445
|
+
- DON't run `python path_to_file.py` to test or serve the app.
|
|
446
|
+
|
|
447
|
+
## Quick Reference
|
|
448
|
+
|
|
449
|
+
### Widget Creation
|
|
450
|
+
```python
|
|
451
|
+
# ✅ Good: Parameter-driven
|
|
452
|
+
widget = pn.widgets.Select.from_param(self.param.model_type, name="Model Type")
|
|
453
|
+
|
|
454
|
+
# ❌ Avoid: Manual management with links
|
|
455
|
+
widget = pn.widgets.Select(options=['A', 'B'], value='A')
|
|
456
|
+
widget.link(self, value='model_type') # Hard to reason about
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Reactive Updates Pattern
|
|
460
|
+
|
|
461
|
+
```python
|
|
462
|
+
# ✅ BEST: Static pane with reactive content (for classes)
|
|
463
|
+
class MyComponent(pn.viewable.Viewer):
|
|
464
|
+
value = param.Number(default=10)
|
|
465
|
+
|
|
466
|
+
def __init__(self, **params):
|
|
467
|
+
super().__init__(**params)
|
|
468
|
+
self._plot_pane = pn.pane.Matplotlib(self._create_plot)
|
|
469
|
+
|
|
470
|
+
@param.depends('value')
|
|
471
|
+
def _create_plot(self):
|
|
472
|
+
return create_plot(self.value) # Returns plot only, not pane
|
|
473
|
+
|
|
474
|
+
# ✅ GOOD: pn.bind for functions
|
|
475
|
+
slider = pn.widgets.IntSlider(value=10)
|
|
476
|
+
plot_pane = pn.pane.Matplotlib(pn.bind(create_plot, slider))
|
|
477
|
+
|
|
478
|
+
# ❌ AVOID: Recreating panes and other components directly. This causes flickering.
|
|
479
|
+
@param.depends('value')
|
|
480
|
+
def view(self):
|
|
481
|
+
return pn.pane.Matplotlib(create_plot(self.value)) # DON'T!
|
|
482
|
+
|
|
483
|
+
# ❌ AVOID: Updating panes and other components directly. This makes it hard to reason about application flow and state
|
|
484
|
+
@param.depends('value', watch=True)
|
|
485
|
+
def update_plot(self):
|
|
486
|
+
self.plot_pane.object = create_plot(self.value)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Static Components Pattern
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
# DO: Create static layout with reactive content
|
|
493
|
+
def _get_kpi_card(self):
|
|
494
|
+
return pn.pane.HTML(
|
|
495
|
+
pn.Column(
|
|
496
|
+
"📊 Key Performance Metrics",
|
|
497
|
+
self.kpi_value # Reactive reference
|
|
498
|
+
),
|
|
499
|
+
styles={"padding": "20px", "border": "1px solid #ddd"},
|
|
500
|
+
sizing_mode="stretch_width"
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
@param.depends("characters")
|
|
504
|
+
def kpi_value(self):
|
|
505
|
+
return f"The kpi is {self.characters}"
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Other Guidance
|
|
509
|
+
|
|
510
|
+
### CheckButtonGroup
|
|
511
|
+
|
|
512
|
+
- DO arrange vertically when displaying `CheckButtonGroup` in a sidebar `CheckButtonGroup(..., vertical=True)`.
|
|
513
|
+
|
|
514
|
+
### Tabulator
|
|
515
|
+
|
|
516
|
+
- DO set `Tabulator.disabled=True` unless you would like the user to be able to edit the table.
|
|
517
|
+
|
|
518
|
+
### Bind
|
|
519
|
+
|
|
520
|
+
- DON't bind a function to nothing: `pn.bind(some_func)`. Just run the function instead `some_func()`.
|
|
521
|
+
|
|
522
|
+
## Plotting
|
|
523
|
+
|
|
524
|
+
- DO use bar charts over pie Charts.
|
|
525
|
+
|
|
526
|
+
### HoloViews/hvPlot
|
|
527
|
+
|
|
528
|
+
- DO let Panel control the renderer theme
|
|
529
|
+
- DON'T set `hv.renderer('bokeh').theme = 'dark_minimal'`
|
|
530
|
+
|
|
531
|
+
DO follow the hvplot and holoviews best practice guides!
|
|
532
|
+
|
|
533
|
+
### Plotly
|
|
534
|
+
|
|
535
|
+
Do set the template (theme) depending on the `theme` of the app.
|
|
536
|
+
|
|
537
|
+
```python
|
|
538
|
+
def create_plot(self) -> go.Figure:
|
|
539
|
+
fig = ...
|
|
540
|
+
template = "plotly_dark" if pn.state.theme=="dark" else "plotly_white"
|
|
541
|
+
fig.update_layout(
|
|
542
|
+
template=template, # Change template to align with the theme
|
|
543
|
+
paper_bgcolor='rgba(0,0,0,0)', # Change to transparent background to align with the app background
|
|
544
|
+
plot_bgcolor='rgba(0,0,0,0)' # Change to transparent background to align with the app background
|
|
545
|
+
)
|
|
546
|
+
return fig
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Date time widgets
|
|
550
|
+
|
|
551
|
+
When comparing to date or time values to Pandas series convert to `Timestamp`:
|
|
552
|
+
|
|
553
|
+
```python
|
|
554
|
+
start_date, end_date = self.date_range
|
|
555
|
+
# DO convert date objects to pandas Timestamp for proper comparison
|
|
556
|
+
start_date = pd.Timestamp(start_date)
|
|
557
|
+
end_date = pd.Timestamp(end_date)
|
|
558
|
+
filtered = filtered[
|
|
559
|
+
(filtered['date'] >= start_date) &
|
|
560
|
+
(filtered['date'] <= end_date)
|
|
561
|
+
]
|
|
562
|
+
```
|