panel
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePanel Development Skills
Panel开发技能
This document provides best practices for developing dashboards and data apps with HoloViz Panel in Python .py files.
Please develop as an Expert Python and Panel Developer developing advanced data-driven, analytics and testable dashboards and analytics apps would do. Keep the code short, concise, documented, testable and professional.
本文档提供了在Python .py文件中使用HoloViz Panel开发仪表盘和数据应用的最佳实践。
请以资深Python和Panel开发者的身份进行开发,打造高级数据驱动、可测试的仪表盘和分析应用。保持代码简洁、有文档注释、可测试且专业。
Dependencies
依赖项
Core dependencies provided with the Python package:
panel- panel: Core application framework
- param: A declarative approach to creating classes with typed, validated, and documented parameters. Fundamental to Panel's reactive programming model.
Optional panel-extensions:
- panel-material-ui: Modern Material UI components. To replace the panel native widgets within the next two years.
- panel-graphic-walker: Modern Tableau like interface. Can offload computations to the server and thus scale to large datasets.
Optional dependencies from the HoloViz Ecosystem:
- 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.
- 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.
- 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.
- 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.
- holoviz-mcp: Model Context Protocol server for HoloViz ecosystem. Provides access to detailed documentation, component search and agent skills.
- hvplot: High-level plotting API with Pandas -like syntax. Best for: quick exploratory visualizations, interactive plots from DataFrames/Xarray, when you want interactivity without verbose code. Built on HoloViews.
.plot() - hvsampledata: Shared datasets for the HoloViz projects.
Optional dependencies from the wider PyData Ecosystem:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- watchfiles: Enables high performance file watching and autoreload for the panel server.
panel- panel: 核心应用框架
- param: 一种声明式方法,用于创建带有类型化、已验证和已记录参数的类,是Panel响应式编程模型的基础。
可选的Panel扩展:
- panel-material-ui: 现代Material UI组件,将在未来两年内替代Panel原生小部件。
- panel-graphic-walker: 类Tableau的现代界面,可将计算任务卸载到服务器,从而支持大规模数据集。
HoloViz生态系统的可选依赖项:
- colorcet: 感知均匀的颜色映射集合,最适用于:需要准确颜色表示的科学可视化、避免彩虹色映射、无障碍配色方案。可与hvPlot、HoloViews、Matplotlib、Bokeh集成。
- datashader: 将大规模数据集(百万+数据点)渲染为图像以进行可视化,最适用于:大数据可视化、地理空间数据集、包含数百万数据点的散点图、密集数据的热图。需要hvPlot或HoloViews作为前端。
- geoviews: 支持地图投影和瓦片源的地理数据可视化,最适用于:地理/地理空间绘图、基于地图的仪表盘、需要坐标系和投影的场景。基于HoloViews构建,可与hvPlot无缝协作。
- holoviews: 声明式数据可视化库,支持可组合元素,最适用于:复杂的多层绘图、高级交互(联动刷选、选择)、需要对绘图组合进行精细控制的场景、科学可视化。比hvPlot功能更强大,但学习曲线更陡峭。
- holoviz-mcp: HoloViz生态系统的模型上下文协议服务器,提供详细文档、组件搜索和技能访问。
- hvplot: 类Pandas 语法的高层级绘图API,最适用于:快速探索性可视化、从DataFrames/Xarray生成交互式绘图、无需冗长代码即可实现交互性的场景。基于HoloViews构建。
.plot() - hvsampledata: HoloViz项目的共享数据集。
更广泛的PyData生态系统的可选依赖项:
- altair: 声明式、基于图形语法的可视化库,最适用于:统计可视化、交互式探索图表、需要Vega-Lite丰富图表库的场景。可与Pandas/Polars DataFrames良好协作。
- dask: 并行计算库,用于将Pandas DataFrames扩展到内存之外,最适用于:处理大于RAM的数据集、跨多个核心/机器的并行计算、惰性评估工作流。
- duckdb: 高性能分析型SQL数据库,最适用于:对DataFrames执行快速SQL查询、对大规模数据集进行聚合、需要SQL接口的场景、OLAP风格分析。在分析查询方面比Pandas快得多。
- matplotlib: 低层级、高度可定制的绘图库,最适用于:出版物级别的静态绘图、对可视化的每个方面进行细粒度控制、科学绘图、需要像素级完美控制的场景。
- pandas: 行业标准的表格数据DataFrame库,最适用于:数据清洗、转换、时间序列分析、可放入内存的数据集。是大多数数据工作的默认选择。
- Plotly: 交互式、出版物级别的可视化库,最适用于:3D绘图、复杂交互式图表、动画、需要悬停工具提示和交互性的场景。可与Dash和Panel良好协作。
- polars: 用Rust编写的现代、快速DataFrame库,最适用于:高性能数据处理、可放入内存但需要速度的数据集、需要惰性评估的场景、比Pandas内存效率更高。
- xarray: N维标记数组和数据集库,最适用于:多维科学数据(气候、卫星图像)、具有多个维度和坐标的数据、NetCDF/HDF5文件、地理空间栅格数据。
- watchfiles: 为Panel服务器启用高性能文件监视和自动重载。
Common Use Cases
常见用例
- Real-time Monitoring Dashboards: Live metrics and KPI displays
- Data Exploration Tools: Interactive data analysis applications
- Configuration Interfaces: Complex multi-step configuration UIs
- Data Input Applications: Validated form-based data collection
- Report Viewers: Interactive report generation and browsing
- Administrative Interfaces: Internal tools for data management
- 实时监控仪表盘: 实时指标和KPI展示
- 数据探索工具: 交互式数据分析应用
- 配置界面: 复杂的多步骤配置UI
- 数据输入应用: 基于表单的验证式数据收集
- 报告查看器: 交互式报告生成与浏览
- 管理界面: 用于数据管理的内部工具
Installation
安装
bash
pip install panel watchfiles hvplot hvsampledataFor development in .py files DO always include watchfiles for hotreload.
bash
pip install panel watchfiles hvplot hvsampledata在.py文件中进行开发时,务必包含watchfiles以实现热重载。
Best Practice Hello World App
最佳实践:Hello World应用
Let's describe our best practices via a basic Hello World App:
python
undefined我们通过一个基础的Hello World应用来介绍最佳实践:
python
undefinedDO import panel as pn
DO import panel as pn
import panel as pn
import param
import panel as pn
import param
DO always run pn.extension
DO always run pn.extension
DO remember to add any imports needed by panes, e.g. pn.extension("tabulator", "plotly", ...)
DO remember to add any imports needed by panes, e.g. pn.extension("tabulator", "plotly", ...)
DON'T add "bokeh" as an extension. It is not needed.
DON'T add "bokeh" as an extension. It is not needed.
Do use throttled=True when using slider unless you have a specific reason not to
Do use throttled=True when using slider unless you have a specific reason not to
pn.extension(throttled=True)
pn.extension(throttled=True)
DO organize functions to extract data separately as your app grows. Eventually in a separate data.py file.
DO organize functions to extract data separately as your app grows. Eventually in a separate data.py file.
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.
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.
DO add a ttl (time to live argument) for expensive data loading that changes over time
DO add a ttl (time to live argument) for expensive data loading that changes over time
@pn.cache(max_items=3)
def extract(n=5):
return "Hello World" + "⭐" * n
text = extract()
text_len = len(text)
@pn.cache(max_items=3)
def extract(n=5):
return "Hello World" + "⭐" * n
text = extract()
text_len = len(text)
DO organize functions to transform data separately as your app grows. Eventually in a separate transformations.py file
DO organize functions to transform data separately as your app grows. Eventually in a separate transformations.py file
DO add caching to speed up expensive data transformations
DO add caching to speed up expensive data transformations
@pn.cache(max_items=3)
def transform(data: str, count: int=5)->str:
count = min(count, len(data))
return data[:count]
@pn.cache(max_items=3)
def transform(data: str, count: int=5)->str:
count = min(count, len(data))
return data[:count]
DO organize functions to create plots separately as your app grows. Eventually in a separate plots.py file.
DO organize functions to create plots separately as your app grows. Eventually in a separate plots.py file.
DO organize custom components and views separately as your app grows. Eventually in separate components.py or views.py file(s).
DO organize custom components and views separately as your app grows. Eventually in separate components.py or views.py file(s).
DO use param.Parameterized, pn.viewable.Viewer or similar approach to create new components and apps with state and reactivity
DO use param.Parameterized, pn.viewable.Viewer or similar approach to create new components and apps with state and reactivity
class HelloWorld(pn.viewable.Viewer):
# DO define parameters to hold state and drive the reactivity
characters = param.Integer(default=text_len, bounds=(0, text_len), doc="Number of characters to display")
def __init__(self, **params):
super().__init__(**params)
# DO use sizing_mode="stretch_width" for components unless "fixed" or other sizing_mode is specifically needed
with pn.config.set(sizing_mode="stretch_width"):
# DO create widgets using `.from_param` method
self._characters_input = pn.widgets.IntSlider.from_param(self.param.characters, margin=(10,20))
# DO Collect input widgets into horizontal, columnar layout unless other layout is specifically needed
self._inputs = pn.Column(self._characters_input, max_width=300)
# CRITICAL: Create panes ONCE with reactive content
# DON'T recreate panes and layouts in @param.depends methods - causes flickering!
# DO bind reactive methods/functions to panes for smooth updates
self._output_pane = pn.pane.Markdown(
self.model, # Reactive method reference
sizing_mode="stretch_width"
)
# DO collect output components into some layout like Column, Row, FlexBox or Grid depending on use case
self._outputs = pn.Column(self._output_pane)
# DO collect all of your components into a combined layout useful for displaying in notebooks etc.
self._panel = pn.Row(self._inputs, self._outputs)
# 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.
# DO add a ttl (time to live argument) for expensive data loading that changes over time.
@pn.cache(max_items=3)
# DO prefer .depends over .bind over .rx for reactivity methods on Parameterized classes as it can be typed and documented
# DON'T use `watch=True` or `.watch(...)` methods to update UI components directly.
# DO use `watch=True` or `.watch(...)` for updating the state parameters or triggering side effects like saving files or sending email.
@param.depends("characters")
def model(self):
# CRITICAL: Return ONLY the content, NOT the layout/pane
# The pane was created once in __init__, this just updates its content
return transform(text, self.characters)
# DO use `watch=True` or `.watch(...)` for updating the state parameters or triggering side effects like saving files or sending email.
@param.depends("characters", watch=True)
def _inform_user(self):
print(f"User selected to show {self.characters} characters.")
# 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.
def __panel__(self):
return self._panel
# DO provide a method to create a .servable app
@classmethod
def create_app(cls, **params):
instance = cls(**params)
# DO use a Template or similar page layout for served apps
template = pn.template.FastListTemplate(
# DO provide a title for the app
title="Hello World App",
# DO provide optional image, optional app description, optional navigation menu, input widgets, optional documentation and optional links in the sidebar
# DO provide as list of components or a list of single horizontal layout like Column as the sidebar by default is 300 px wide
sidebar=[instance._inputs],
# DO provide a list of layouts and output components in the main area of the app.
# DO use Grid or FlexBox layouts for complex dashboard layouts instead of combination Rows and Columns.
main=[instance._outputs],
# DO set main_layout=None for modern layout
main_layout=None,
)
return templateclass HelloWorld(pn.viewable.Viewer):
# DO define parameters to hold state and drive the reactivity
characters = param.Integer(default=text_len, bounds=(0, text_len), doc="Number of characters to display")
def __init__(self, **params):
super().__init__(**params)
# DO use sizing_mode="stretch_width" for components unless "fixed" or other sizing_mode is specifically needed
with pn.config.set(sizing_mode="stretch_width"):
# DO create widgets using `.from_param` method
self._characters_input = pn.widgets.IntSlider.from_param(self.param.characters, margin=(10,20))
# DO Collect input widgets into horizontal, columnar layout unless other layout is specifically needed
self._inputs = pn.Column(self._characters_input, max_width=300)
# CRITICAL: Create panes ONCE with reactive content
# DON'T recreate panes and layouts in @param.depends methods - causes flickering!
# DO bind reactive methods/functions to panes for smooth updates
self._output_pane = pn.pane.Markdown(
self.model, # Reactive method reference
sizing_mode="stretch_width"
)
# DO collect output components into some layout like Column, Row, FlexBox or Grid depending on use case
self._outputs = pn.Column(self._output_pane)
# DO collect all of your components into a combined layout useful for displaying in notebooks etc.
self._panel = pn.Row(self._inputs, self._outputs)
# 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.
# DO add a ttl (time to live argument) for expensive data loading that changes over time.
@pn.cache(max_items=3)
# DO prefer .depends over .bind over .rx for reactivity methods on Parameterized classes as it can be typed and documented
# DON'T use `watch=True` or `.watch(...)` methods to update UI components directly.
# DO use `watch=True` or `.watch(...)` for updating the state parameters or triggering side effects like saving files or sending email.
@param.depends("characters")
def model(self):
# CRITICAL: Return ONLY the content, NOT the layout/pane
# The pane was created once in __init__, this just updates its content
return transform(text, self.characters)
# DO use `watch=True` or `.watch(...)` for updating the state parameters or triggering side effects like saving files or sending email.
@param.depends("characters", watch=True)
def _inform_user(self):
print(f"User selected to show {self.characters} characters.")
# 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.
def __panel__(self):
return self._panel
# DO provide a method to create a .servable app
@classmethod
def create_app(cls, **params):
instance = cls(**params)
# DO use a Template or similar page layout for served apps
template = pn.template.FastListTemplate(
# DO provide a title for the app
title="Hello World App",
# DO provide optional image, optional app description, optional navigation menu, input widgets, optional documentation and optional links in the sidebar
# DO provide as list of components or a list of single horizontal layout like Column as the sidebar by default is 300 px wide
sidebar=[instance._inputs],
# DO provide a list of layouts and output components in the main area of the app.
# DO use Grid or FlexBox layouts for complex dashboard layouts instead of combination Rows and Columns.
main=[instance._outputs],
# DO set main_layout=None for modern layout
main_layout=None,
)
return templateDON'T provide a if __name__ == "__main__":
method to serve the app with python
if __name__ == "__main__":pythonDON'T provide a if __name__ == "__main__":
method to serve the app with python
if __name__ == "__main__":pythonDO provide a method to serve the app with panel serve
panel serveDO provide a method to serve the app with panel serve
panel serveif pn.state.served:
# Mark components to be displayed in the app with .servable()
HelloWorld.create_app().servable()
DO serve the app with
```bash
panel serve path_to_this_file.py --show --devDON'T serve with .
python path_to_this_file.pyif pn.state.served:
# Mark components to be displayed in the app with .servable()
HelloWorld.create_app().servable()
请使用以下命令启动应用:
```bash
panel serve path_to_this_file.py --show --dev不要使用启动应用。
python path_to_this_file.pyBest Practice Hello World Tests
最佳实践:Hello World测试
With panel you can easily create tests to test user behaviour without having to write client side tests.
DO always create separate tests in the folder:
testspython
undefined使用Panel,您可以轻松创建测试来验证用户行为,而无需编写客户端测试。
务必在文件夹中创建单独的测试:
testspython
undefinedDO put tests in a separate test file.
DO put tests in a separate test file.
DO always test that the reactivity works as expected
DO always test that the reactivity works as expected
def test_characters_reactivity():
"""
Test characters reactivity.
"""
# DO test the default values of bound
hello_world = HelloWorld()
# DO test the reactivity of bound methods when parameters change
assert hello_world.model() == text[:hello_world.characters]
hello_world.characters = 5
assert hello_world.model() == text[:5]
hello_world.characters = 3
assert hello_world.model() == text[:3]
DO run the tests with:
```bash
pytest tests/path/to/test_file.pyDO fix any errors identified.
def test_characters_reactivity():
"""
Test characters reactivity.
"""
# DO test the default values of bound
hello_world = HelloWorld()
# DO test the reactivity of bound methods when parameters change
assert hello_world.model() == text[:hello_world.characters]
hello_world.characters = 5
assert hello_world.model() == text[:5]
hello_world.characters = 3
assert hello_world.model() == text[:3]
使用以下命令运行测试:
```bash
pytest tests/path/to/test_file.py修复所有发现的错误。
Key Patterns
核心模式
Parameter-Driven Architecture
参数驱动架构
- DO use or
param.Parameterizedclasses to organize and manage statepn.viewable.Viewer - DO create widgets with method. DON'T do this for panes, i.e. pn.pane.Str has no from_param method.
.from_param() - DO use for reactive methods
@param.depends() - DO use to update parameter/ state values and for side-effects like sending an email.
@param.depends(..., watch=True) - DO group related parameters in separate or
ParameterizedclassesViewable
python
undefined- 请使用或
param.Parameterized类来组织和管理状态pn.viewable.Viewer - 请使用方法创建小部件。不要为窗格(pane)使用此方法,例如pn.pane.Str没有from_param方法。
.from_param() - 请为响应式方法使用
@param.depends() - 请使用来更新参数/状态值,以及触发保存文件或发送电子邮件等副作用。
@param.depends(..., watch=True) - 请将相关参数分组到单独的或
Parameterized类中Viewable
python
undefined❌ AVOID: Updating panes and other components directly. This makes it hard to reason about application flow and state
❌ 避免:直接更新窗格和其他组件。这会使应用流程和状态的推理变得困难
@param.depends('value', watch=True)
def update_plot(self):
self.output_pane.object = transform(text, self.characters)
undefined@param.depends('value', watch=True)
def update_plot(self):
self.output_pane.object = transform(text, self.characters)
undefinedCreate Static Layout with Reactive Content (CRITICAL)
创建静态布局与响应式内容(关键)
The Golden Rule: Create layout structure ONCE, update content REACTIVELY
This pattern eliminates flickering and creates professional Panel applications:
python
undefined黄金法则:一次性创建布局结构,响应式更新内容
此模式可消除闪烁,打造专业的Panel应用:
python
undefined✅ CORRECT: Create panes ONCE in init, bind reactive content
✅ 正确:一次性创建窗格,绑定响应式内容
class Dashboard(pn.viewable.Viewer):
filter_value = param.String(default="all")
chart = param.Parameter()
def __init__(self, **params):
super().__init__(**params)
# 1. Create static panes with reactive content
self._summary_pane = pn.pane.Markdown(self._summary_text)
self._chart_pane = pn.pane.HoloViews(self.param.chart)
# 2. Create static layout structure
self._layout = pn.Column(
"# Dashboard", # Static title
self._summary_pane, # Reactive content
self._chart_pane, # Reactive content
)
# ✅ Good: Reactive content method
# Will be run multiple times when filter_value updates if multiple panes or reactive functions depend on the _summary_text method
@param.depends("filter_value")
def _summary_text(self):
# Returns string content only, NOT a pane
return f"**Count**: {len(self._get_data())}"
# ✅ Good: Reactive update of chart parameter
# Will be run only one time when filter_value updates - even if multiple panes or reactive functions depend on the chart value
@param.depends("filter_value", watch=True, on_init=True)
def _update_chart(self):
# updates the chart object only, NOT a pane
self.chart = self._get_data().hvplot.bar()
def __panel__(self):
return self._layoutclass Dashboard(pn.viewable.Viewer):
filter_value = param.String(default="all")
chart = param.Parameter()
def __init__(self, **params):
super().__init__(**params)
# 1. 创建带有响应式内容的静态窗格
self._summary_pane = pn.pane.Markdown(self._summary_text)
self._chart_pane = pn.pane.HoloViews(self.param.chart)
# 2. 创建静态布局结构
self._layout = pn.Column(
"# Dashboard", # 静态标题
self._summary_pane, # 响应式内容
self._chart_pane, # 响应式内容
)
# ✅ 良好:响应式内容方法
# 当filter_value更新时,如果多个窗格或响应式函数依赖于_summary_text方法,该方法会被多次调用
@param.depends("filter_value")
def _summary_text(self):
# 仅返回内容,不返回窗格
return f"**Count**: {len(self._get_data())}"
# ✅ 良好:响应式更新图表参数
# 当filter_value更新时,该方法仅会被调用一次 - 即使多个窗格或响应式函数依赖于chart值
@param.depends("filter_value", watch=True, on_init=True)
def _update_chart(self):
# 仅更新chart对象,不更新窗格
self.chart = self._get_data().hvplot.bar()
def __panel__(self):
return self._layout❌ WRONG: Recreating layout in @param.depends - causes flickering!
❌ 错误:在@param.depends中重新创建布局 - 会导致闪烁!
class BadDashboard(pn.viewable.Viewer):
filter_value = param.String(default="all")
@param.depends("filter_value")
def view(self):
# DON'T recreate panes/layouts on every parameter change!
return pn.Column(
"# Dashboard",
pn.pane.Markdown(f"**Count**: {len(self._get_data())}"),
pn.pane.HoloViews(self._get_data().hvplot.bar()),
)
**Why This Matters:**
- ✅ Smooth updates without layout reconstruction
- ✅ No flickering - seamless transitions
- ✅ Better performance - avoids unnecessary DOM updates
- ✅ Professional UX
**Key Rules:**
1. DO create main layout structure and panes ONCE in `__init__`
2. DO bind panes to reactive methods or parameters (DON'T recreate them)
3. Reactive methods should return CONTENT only (strings, plots, dataframes), NOT panes/layouts
4. Use `@param.depends` for reactive methods that update pane contentclass BadDashboard(pn.viewable.Viewer):
filter_value = param.String(default="all")
@param.depends("filter_value")
def view(self):
# 不要在每次参数变化时重新创建窗格/布局!
return pn.Column(
"# Dashboard",
pn.pane.Markdown(f"**Count**: {len(self._get_data())}"),
pn.pane.HoloViews(self._get_data().hvplot.bar()),
)
**为什么这很重要:**
- ✅ 无需重建布局即可实现平滑更新
- ✅ 无闪烁 - 无缝过渡
- ✅ 性能更佳 - 避免不必要的DOM更新
- ✅ 专业的用户体验
**核心规则:**
1. 请在`__init__`中一次性创建主布局结构和窗格
2. 请将窗格绑定到响应式方法或参数(不要重新创建它们)
3. 响应式方法应仅返回内容(字符串、绘图、数据框),而不是窗格/布局
4. 使用`@param.depends`来更新窗格内容的响应式方法Widgets
小部件
Use
- ,
pn.widgets.IntSlider,pn.widgets.Selectand other widgets for inputpn.widgets.DateRangeSlider - to display tabular data like DataFrames
pn.widgets.Tabulator
使用以下组件:
- 、
pn.widgets.IntSlider、pn.widgets.Select等小部件用于输入pn.widgets.DateRangeSlider - 用于显示表格数据(如DataFrames)
pn.widgets.Tabulator
Panes
窗格
Use panes to display data:
- and
pn.pane.Markdownto display stringspn.pane.HTML - ,
pn.pane.HoloViews,pn.pane.Plotlyorpn.pane.Matplotlibto display plotspn.pane.ECharts
使用窗格来显示数据:
- 和
pn.pane.Markdown用于显示字符串pn.pane.HTML - 、
pn.pane.HoloViews、pn.pane.Plotly或pn.pane.Matplotlib用于显示绘图pn.pane.ECharts
Layouts
布局
Use layouts to layout components:
- DO use ,
pn.Column,pn.Row,pn.Tabsfor layoutspn.Accordion
使用布局来排列组件:
- 请使用、
pn.Column、pn.Row、pn.Tabs进行布局pn.Accordion
Templates
模板
- DO use or other templates for served apps:
pn.template.FastListTemplate
python
template = pn.template.FastListTemplate(
title="Hello World App",
sidebar=[instance._inputs],
main=[instance._outputs],
main_layout=None,
)- In the , DO use the order: 1) optional logo, 2) description, 3) input widgets, 4) documentation
sidebar - In the , DO make sure components stretch width.
sidebar - Do set for a modern styling.
main_layout=None
- 请为要部署的应用使用或其他模板:
pn.template.FastListTemplate
python
template = pn.template.FastListTemplate(
title="Hello World App",
sidebar=[instance._inputs],
main=[instance._outputs],
main_layout=None,
)- 在中,请按照以下顺序排列:1) 可选Logo,2) 描述,3) 输入小部件,4) 文档
sidebar - 在中,请确保组件宽度自适应。
sidebar - 请设置以使用现代样式。
main_layout=None
Responsive Design
响应式设计
- DO use by default:
sizing_mode="stretch_width"
python
with pn.config.set(sizing_mode="stretch_width"):
character_input = pn.widgets...
output_pane = pn.pane....- DO use ,
FlexBoxorGridSpecfor complex, responsive grid layoutsGridBox - DO set appropriate ,
min_width,min_heightandmax_widthto prevent layout collapsemax_height
- 请默认使用:
sizing_mode="stretch_width"
python
with pn.config.set(sizing_mode="stretch_width"):
character_input = pn.widgets...
output_pane = pn.pane....- 请使用、
FlexBox或GridSpec来构建复杂的响应式网格布局GridBox - 请设置适当的、
min_width、min_height和max_width以防止布局崩溃max_height
Extensions
扩展
DO remember to add extensions like "tabulator", "plotly" etc. to to make sure their Javascript is loaded:
pn.extensionpython
undefined请记得将"tabulator"、"plotly"等扩展添加到中,以确保其Javascript已加载:
pn.extensionpython
undefined✅ Good
✅ 正确
pn.extension("tabulator", "plotly")
DON'T add "bokeh". It's already loaded:
```pythonpn.extension("tabulator", "plotly")
不要添加"bokeh",它已自动加载:
```python❌ Bad
❌ 错误
pn.extension("bokeh")
undefinedpn.extension("bokeh")
undefinedServable()
Servable()
DO make the main component to include it in the served app and use to run the main method when the app is panel serve'd.
.servable()pn.state.servedpython
undefined请将主组件标记为以将其包含在部署的应用中,并使用在应用通过启动时运行主方法。
.servable()pn.state.servedpanel servepython
undefined✅ Correct:
✅ 正确:
if pn.state.served:
main().servable()
if pn.state.served:
main().servable()
❌ Incorrect:
❌ 错误:
if name == "main":
main().servable()
if name == "main":
main().servable()
❌ Don't: Works, but not how we want to serve the app:
❌ 不推荐:可以运行,但不符合我们的应用部署方式:
if name == "main":
main().show()
undefinedif name == "main":
main().show()
undefinedPerformance Optimization
性能优化
- Defer load: Defer load to after the app is shown to the user:
pn.extension(defer_load=True, loading_indicator=True, ...) - Lazy-load components using Tabs or Accordion for heavy content
- Use caching with decorator for expensive computations
@pn.cache - Use async/await: Implement asynchronous patterns for I/O operations
- Use faster frameworks: Replace slower Pandas with faster Polars or DuckDB
- Offload to threads: Consider using threading for CPU-intensive tasks
- Offload to external processes: Consider offloading heavy computations to external processes like databases, scheduled (airflow) jobs, REST apis etc.
- Profile callbacks with to identify bottlenecks
@pn.io.profiler
If you experience memory issues, make sure to:
- Limit history: Cap data history sizes in streaming applications
- Clear caches: Periodically call
pn.state.clear_caches() - Restart periodically: Schedule application restarts for long-running production apps
- Profile memory: Use memory profilers (memory_profiler, tracemalloc) to identify leaks
- 延迟加载:在应用显示给用户后再延迟加载:
pn.extension(defer_load=True, loading_indicator=True, ...) - 懒加载组件:对内容较重的部分使用Tabs或Accordion进行懒加载
- 使用缓存:对昂贵的计算使用装饰器
@pn.cache - 使用async/await:为I/O操作实现异步模式
- 使用更快的框架:将较慢的Pandas替换为更快的Polars或DuckDB
- 卸载到线程:考虑对CPU密集型任务使用线程
- 卸载到外部进程:考虑将繁重的计算卸载到外部进程,如数据库、调度(Airflow)作业、REST API等
- 分析回调:使用来识别性能瓶颈
@pn.io.profiler
如果遇到内存问题,请确保:
- 限制历史记录:在流应用中限制数据历史记录的大小
- 清除缓存:定期调用
pn.state.clear_caches() - 定期重启:为长期运行的生产应用安排重启
- 分析内存:使用内存分析工具(memory_profiler、tracemalloc)来识别内存泄漏
Code Organization
代码组织
- Separate concerns: Keep UI code separate from business logic using Param classes
- Create reusable components: Extract common patterns into functions or classes
- Use templates for consistent application structure across pages
- Organize modules: Group related components and utilities
- Document parameters: Add clear docstrings to Parameterized classes
- 关注点分离:使用Param类将UI代码与业务逻辑分离
- 创建可重用组件:将通用模式提取为函数或类
- 使用模板:在多个页面之间保持一致的应用结构
- 组织模块:将相关组件和工具分组
- 文档化参数:为Parameterized类添加清晰的文档字符串
Workflows
工作流程
Lookup additional information
查找额外信息
- If the HoloViz MCP server tools are available, DO use them to look up documentation and component details. Useful tools: (documentation),
search(find components),pn_search(component details),pn_get(parameter info),pn_params(reference guides),ref_get(best-practice skills).skill_get - If MCP tools are not available but the CLI is installed (also available as
holoviz-mcp), use the equivalent CLI commands:hv,holoviz-mcp search,holoviz-mcp pn search,holoviz-mcp pn get,holoviz-mcp pn params,holoviz-mcp ref get.holoviz-mcp skill get - If neither is available, DO search the web. For example searching the Panel website for related information via https://panel.holoviz.org/search.html?q=Tabulator url.
Tabulator
- 如果HoloViz MCP服务器工具可用,请使用它们来查找文档和组件详情。有用的工具:(文档)、
search(查找组件)、pn_search(组件详情)、pn_get(参数信息)、pn_params(参考指南)、ref_get(最佳实践技能)。skill_get - 如果MCP工具不可用但已安装CLI(也可通过
holoviz-mcp访问),请使用等效的CLI命令:hv、holoviz-mcp search、holoviz-mcp pn search、holoviz-mcp pn get、holoviz-mcp pn params、holoviz-mcp ref get。holoviz-mcp skill get - 如果以上都不可用,请通过网络搜索。例如,通过https://panel.holoviz.org/search.html?q=Tabulator在Panel网站上搜索与相关的信息。
Tabulator
Test the app with pytest
使用pytest测试应用
DO add tests to the folder and run them with .
testspytest tests/path/to/test_file.py- DO structure your code with Parameterized components, so that reactivity and user interactions can be tested easily.
- DO separate UI logic from business logic to enable unit testing
- DO separate data extraction, data transformation, plots generation, custom components and views, styles etc. to enable unit testing as your app grows
- DO fix any test errors and rerun the tests
- DO run the tests and fix errors before serving the app and asking the user to run manual tests
请在文件夹中添加测试,并使用运行测试。
testspytest tests/path/to/test_file.py- 请使用Parameterized组件构建代码,以便轻松测试响应性和用户交互
- 请将UI逻辑与业务逻辑分离,以支持单元测试
- 随着应用的增长,请将数据提取、数据转换、绘图生成、自定义组件和视图、样式等分离,以支持单元测试
- 请修复所有测试错误并重新运行测试
- 请在部署应用并要求用户进行手动测试之前运行测试并修复错误
Test the app manually with panel serve
使用panel serve手动测试应用
DO always start and keep running a development server with hot reload while developing!
panel serve path_to_file.py --dev --show- Due to flag, a browser tab will automatically open showing your app.
--show - Due to flag, the panel server and app will automatically reload if you change the code.
--dev - The app will be served at http://localhost:5006/.
- DO make sure the correct virtual environment is activated before serving the app.
- DO fix any errors that show up in the terminal. Consider adding new tests to ensure they don't happen again.
- DON'T stop or restart the server after changing the code. The app will automatically reload.
- If you see 'Cannot start Bokeh server, port 5006 is already in use' in the terminal, DO serve the app on another port with flag.
--port {port-number} - DO remind the user to test the application on multiple screen sizes (desktop, tablet, mobile)
- DON'T use legacy flag
--autoreload - DON't run to test or serve the app.
python path_to_file.py - If you close the server to run other commands DO remember to restart it.
开发过程中,请始终启动并运行开发服务器以实现热重载!
panel serve path_to_file.py --dev --show- 由于标志,浏览器标签页将自动打开并显示您的应用。
--show - 由于标志,Panel服务器和应用将在您修改代码后自动重载。
--dev - 应用将部署在http://localhost:5006/。
- 请确保在启动应用前已激活正确的虚拟环境。
- 请修复终端中显示的所有错误。考虑添加新测试以确保这些错误不再发生。
- 修改代码后,请不要停止或重启服务器。应用将自动重载。
- 如果终端中显示'Cannot start Bokeh server, port 5006 is already in use',请使用标志在其他端口部署应用。
--port {port-number} - 请提醒用户在多种屏幕尺寸(桌面、平板、移动设备)上测试应用
- 不要使用旧版标志
--autoreload - 不要使用来测试或部署应用。
python path_to_file.py - 如果您关闭服务器以运行其他命令,请记得重新启动它。
Quick Reference
快速参考
Widget Creation
小部件创建
python
undefinedpython
undefined✅ Good: Parameter-driven
✅ 良好:参数驱动
widget = pn.widgets.Select.from_param(self.param.model_type, name="Model Type")
widget = pn.widgets.Select.from_param(self.param.model_type, name="Model Type")
❌ Avoid: Manual management with links
❌ 避免:使用链接进行手动管理
widget = pn.widgets.Select(options=['A', 'B'], value='A')
widget.link(self, value='model_type') # Hard to reason about
undefinedwidget = pn.widgets.Select(options=['A', 'B'], value='A')
widget.link(self, value='model_type') # 难以推理
undefinedReactive Updates Pattern
响应式更新模式
python
undefinedpython
undefined✅ BEST: Static pane with reactive content (for classes)
✅ 最佳:静态窗格与响应式内容(适用于类)
class MyComponent(pn.viewable.Viewer):
value = param.Number(default=10)
def __init__(self, **params):
super().__init__(**params)
self._plot_pane = pn.pane.Matplotlib(self._create_plot)
@param.depends('value')
def _create_plot(self):
return create_plot(self.value) # Returns plot only, not paneclass MyComponent(pn.viewable.Viewer):
value = param.Number(default=10)
def __init__(self, **params):
super().__init__(**params)
self._plot_pane = pn.pane.Matplotlib(self._create_plot)
@param.depends('value')
def _create_plot(self):
return create_plot(self.value) # 仅返回绘图,不返回窗格✅ GOOD: pn.bind for functions
✅ 良好:针对函数使用pn.bind
slider = pn.widgets.IntSlider(value=10)
plot_pane = pn.pane.Matplotlib(pn.bind(create_plot, slider))
slider = pn.widgets.IntSlider(value=10)
plot_pane = pn.pane.Matplotlib(pn.bind(create_plot, slider))
❌ AVOID: Recreating panes and other components directly. This causes flickering.
❌ 避免:直接重新创建窗格和其他组件。这会导致闪烁。
@param.depends('value')
def view(self):
return pn.pane.Matplotlib(create_plot(self.value)) # DON'T!
@param.depends('value')
def view(self):
return pn.pane.Matplotlib(create_plot(self.value)) # 不要这样做!
❌ AVOID: Updating panes and other components directly. This makes it hard to reason about application flow and state
❌ 避免:直接更新窗格和其他组件。这会使应用流程和状态的推理变得困难
@param.depends('value', watch=True)
def update_plot(self):
self.plot_pane.object = create_plot(self.value)
undefined@param.depends('value', watch=True)
def update_plot(self):
self.plot_pane.object = create_plot(self.value)
undefinedStatic Components Pattern
静态组件模式
python
undefinedpython
undefinedDO: Create static layout with reactive content
请:创建带有响应式内容的静态布局
def _get_kpi_card(self):
return pn.pane.HTML(
pn.Column(
"📊 Key Performance Metrics",
self.kpi_value # Reactive reference
),
styles={"padding": "20px", "border": "1px solid #ddd"},
sizing_mode="stretch_width"
)
@param.depends("characters")
def kpi_value(self):
return f"The kpi is {self.characters}"
undefineddef _get_kpi_card(self):
return pn.pane.HTML(
pn.Column(
"📊 Key Performance Metrics",
self.kpi_value # 响应式引用
),
styles={"padding": "20px", "border": "1px solid #ddd"},
sizing_mode="stretch_width"
)
@param.depends("characters")
def kpi_value(self):
return f"The kpi is {self.characters}"
undefinedOther Guidance
其他指南
CheckButtonGroup
CheckButtonGroup
- DO arrange vertically when displaying in a sidebar
CheckButtonGroup.CheckButtonGroup(..., vertical=True) - DO set and
button_type="primary".button_style="outline"
- 在侧边栏中显示时,请垂直排列:
CheckButtonGroup。CheckButtonGroup(..., vertical=True) - 请设置和
button_type="primary"。button_style="outline"
Tabulator
Tabulator
- DO set unless you would like the user to be able to edit the table.
Tabulator.disabled=True - DO prefer Tabulator Formatters over Bokeh formatters and Pandas Styling.
- DO prefer Tabulator Editors over Bokeh Editor types
- 除非您希望用户能够编辑表格,否则请设置。
Tabulator.disabled=True - 请优先使用Tabulator格式器而非Bokeh格式器和Pandas样式。
- 请优先使用Tabulator编辑器而非Bokeh编辑器类型
Markdown
Markdown
- DO set to avoid page flickr when hovering over headers.
Markdown.disable_anchors=True
- 请设置以避免悬停在标题上时页面闪烁。
Markdown.disable_anchors=True
Bind
Bind
- DON't bind a function to nothing: . Just run the function instead
pn.bind(some_func).some_func()
- 不要将函数绑定到空值:。直接运行函数
pn.bind(some_func)即可。some_func()
Plotting
绘图
- DO use bar charts over pie Charts.
- 请使用柱状图而非饼图。
HoloViews/hvPlot
HoloViews/hvPlot
DO follow the relevant , and skills/ best practice guides!
hvplotholoviewspanel-holoviews请遵循相关的、和技能/最佳实践指南!
hvplotholoviewspanel-holoviewsMatplotlib
Matplotlib
CRITICAL: On windows set non-interactive backend before importing matplotlib.pyplot:
Why: The backend is required for server-side rendering without display. Panel needs to render plots as images, not interactive GUI windows.
'agg'Extension: DON'T include in - it doesn't require JavaScript loading like Plotly or Bokeh extensions.
'matplotlib'pn.extension()python
undefined关键提示:在Windows系统上,在导入matplotlib.pyplot之前设置非交互式后端:
原因:后端是无显示的服务器端渲染所必需的。Panel需要将绘图渲染为图像,而非交互式GUI窗口。
'agg'扩展:不要在中包含 - 它不需要像Plotly或Bokeh扩展那样加载JavaScript。
pn.extension()'matplotlib'python
undefined✅ CORRECT
✅ 正确
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import panel as pn
pn.extension() # No 'matplotlib' needed
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
import panel as pn
pn.extension() # 不需要'matplotlib'
❌ WRONG
❌ 错误
pn.extension('matplotlib') # Not a Panel extension
**Best Practices**:
- DO use matplotlib for publication-quality static plots
- DO close figures after rendering to prevent memory leaks: `plt.close(fig)`
- DO consider hvPlot or Plotly for interactive plots insteadpn.extension('matplotlib') # 不是Panel扩展
**最佳实践**:
- 请使用matplotlib创建出版物级别的静态绘图
- 渲染后关闭图形以防止内存泄漏:`plt.close(fig)`
- 对于交互式绘图,请考虑使用hvPlot或Plotly替代Plotly
Plotly
Do set the template (theme) depending on the of the app.
themepython
def create_plot(self) -> go.Figure:
fig = ...
template = "plotly_dark" if pn.state.theme=="dark" else "plotly_white"
fig.update_layout(
template=template, # Change template to align with the theme
paper_bgcolor='rgba(0,0,0,0)', # Change to transparent background to align with the app background
plot_bgcolor='rgba(0,0,0,0)' # Change to transparent background to align with the app background
)
return fig请根据应用的设置模板(主题)。
themepython
def create_plot(self) -> go.Figure:
fig = ...
template = "plotly_dark" if pn.state.theme=="dark" else "plotly_white"
fig.update_layout(
template=template, # 更改模板以匹配主题
paper_bgcolor='rgba(0,0,0,0)', # 更改为透明背景以匹配应用背景
plot_bgcolor='rgba(0,0,0,0)' # 更改为透明背景以匹配应用背景
)
return figECharts
ECharts
DO prefer ECharts dict configuration over of pyecharts
CRITICAL: ECharts configurations must be JSON-serializable. Panel uses Bokeh's serialization mechanism which cannot serialize Python functions.
❌ NEVER use Python functions or lambdas in ECharts configuration:
python
undefined请优先使用ECharts字典配置而非pyecharts
关键提示:ECharts配置必须是JSON可序列化的。Panel使用Bokeh的序列化机制,无法序列化Python函数。
❌ 切勿在ECharts配置中使用Python函数或lambda表达式:
python
undefined❌ WRONG: Lambda functions cause SerializationError
❌ 错误:Lambda函数会导致序列化错误
option = {
"tooltip": {
"formatter": lambda params: f"Value: {params['value']}" # DON'T!
},
"xAxis": {
"axisLabel": {
"formatter": lambda value: f"{value}%" # DON'T!
}
},
"series": [{
"animationDelay": lambda idx: idx * 100 # DON'T!
}]
}
✅ **DO use ECharts native string formatters or static values**:
```pythonoption = {
"tooltip": {
"formatter": lambda params: f"Value: {params['value']}" # 不要这样做!
},
"xAxis": {
"axisLabel": {
"formatter": lambda value: f"{value}%" # 不要这样做!
}
},
"series": [{
"animationDelay": lambda idx: idx * 100 # 不要这样做!
}]
}
✅ **请使用ECharts原生字符串格式器或静态值**:
```python✅ CORRECT: Use ECharts template strings
✅ 正确:使用ECharts模板字符串
option = {
"tooltip": {
"formatter": "{b}: {c}" # Template string
},
"xAxis": {
"axisLabel": {
"formatter": "{value}%" # Template string with formatting
}
},
"yAxis": {
"axisLabel": {
"formatter": "${value}" # Dollar sign prefix
}
},
"series": [{
"animationDelay": 100 # Static numeric value
}]
}
**ECharts Formatter Template Syntax**:
- `{a}` - series name
- `{b}` - data name (category)
- `{c}` - data value
- `{d}` - percentage (for pie charts)
- `{value}` - axis value
- Supports prefix/suffix: `'{value}%'`, `'${value}'`, `'{value} units'`
If you need complex formatting logic, pre-process your data in Python before passing to ECharts rather than using formatters.
**Reactive Updates with replaceMerge**:
When updating ECharts dynamically (e.g., filtering data that changes the number of series), ECharts uses a **merge strategy** by default. This can cause old series to persist when series are removed.
✅ **DO use `replaceMerge` option** when series count can change:
```pythonoption = {
"tooltip": {
"formatter": "{b}: {c}" # 模板字符串
},
"xAxis": {
"axisLabel": {
"formatter": "{value}%" # 带格式的模板字符串
}
},
"yAxis": {
"axisLabel": {
"formatter": "${value}" # 美元符号前缀
}
},
"series": [{
"animationDelay": 100 # 静态数值
}]
}
**ECharts格式器模板语法**:
- `{a}` - 系列名称
- `{b}` - 数据名称(类别)
- `{c}` - 数据值
- `{d}` - 百分比(适用于饼图)
- `{value}` - 轴值
- 支持前缀/后缀:`'{value}%'`、`'${value}'`、`'{value} units'`
如果需要复杂的格式逻辑,请在传递给ECharts之前在Python中预处理数据,而非使用格式器。✅ CORRECT: Use replaceMerge to fully replace series on updates
使用replaceMerge进行响应式更新
chart_pane = pn.pane.ECharts(
self._chart_config, # Reactive method or parameter
options={"replaceMerge": ["series"]}, # Replace series array instead of merging
sizing_mode="stretch_width",
height=400,
)
❌ **WITHOUT replaceMerge** - old series persist:
```python当动态更新ECharts时(例如,过滤数据会改变系列数量),ECharts默认使用合并策略。这会导致在移除系列时旧系列仍然存在。
✅ 当系列数量可能变化时,请使用选项:
replaceMergepython
undefined❌ WRONG: Old series remain when filtering reduces series count
✅ 正确:使用replaceMerge在更新时完全替换系列
chart_pane = pn.pane.ECharts(
self._chart_config,
sizing_mode="stretch_width",
)
chart_pane = pn.pane.ECharts(
self._chart_config, # 响应式方法或参数
options={"replaceMerge": ["series"]}, # 替换系列数组而非合并
sizing_mode="stretch_width",
height=400,
)
❌ **不使用replaceMerge** - 旧系列会残留:
```pythonIf config changes from 4 series to 2, ECharts merges and keeps all 4!
❌ 错误:当过滤减少系列数量时,旧系列会保留
Use `replaceMerge` for any chart where:
- Users can filter data (year selectors, category filters)
- The number of series changes dynamically
- Data is grouped by categories that may be added/removed
DO make sure the chart title does not overlap with the rest of the plot including legend.chart_pane = pn.pane.ECharts(
self._chart_config,
sizing_mode="stretch_width",
)
Date time widgets
如果配置从4个系列变为2个,ECharts会合并并保留所有4个系列!
When comparing to date or time values to Pandas series convert to :
Timestamppython
start_date, end_date = self.date_range
在以下场景中使用`replaceMerge`:
- 用户可以过滤数据(年份选择器、类别过滤器)
- 系列数量会动态变化
- 数据按可能添加/删除的类别分组
请确保图表标题不会与绘图的其他部分(包括图例)重叠。DO convert date objects to pandas Timestamp for proper comparison
日期时间小部件
start_date = pd.Timestamp(start_date)
end_date = pd.Timestamp(end_date)
filtered = filtered[
(filtered['date'] >= start_date) &
(filtered['date'] <= end_date)
]
undefined当将日期或时间值与Pandas系列进行比较时,请转换为:
Timestamppython
start_date, end_date = self.date_range—
请将日期对象转换为pandas Timestamp以进行正确比较
—
start_date = pd.Timestamp(start_date)
end_date = pd.Timestamp(end_date)
filtered = filtered[
(filtered['date'] >= start_date) &
(filtered['date'] <= end_date)
]
undefined