panel

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Panel 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
panel
Python package:
  • 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
    .plot()
    -like syntax. Best for: quick exploratory visualizations, interactive plots from DataFrames/Xarray, when you want interactivity without verbose code. Built on HoloViews.
  • 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
Python包提供的核心依赖项:
  • 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
    .plot()
    语法的高层级绘图API,最适用于:快速探索性可视化、从DataFrames/Xarray生成交互式绘图、无需冗长代码即可实现交互性的场景。基于HoloViews构建。
  • 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

常见用例

  1. Real-time Monitoring Dashboards: Live metrics and KPI displays
  2. Data Exploration Tools: Interactive data analysis applications
  3. Configuration Interfaces: Complex multi-step configuration UIs
  4. Data Input Applications: Validated form-based data collection
  5. Report Viewers: Interactive report generation and browsing
  6. Administrative Interfaces: Internal tools for data management
  1. 实时监控仪表盘: 实时指标和KPI展示
  2. 数据探索工具: 交互式数据分析应用
  3. 配置界面: 复杂的多步骤配置UI
  4. 数据输入应用: 基于表单的验证式数据收集
  5. 报告查看器: 交互式报告生成与浏览
  6. 管理界面: 用于数据管理的内部工具

Installation

安装

bash
pip install panel watchfiles hvplot hvsampledata
For 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
undefined

DO 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 template
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 template

DON'T provide a
if __name__ == "__main__":
method to serve the app with
python

DON'T provide a
if __name__ == "__main__":
method to serve the app with
python

DO provide a method to serve the app with
panel serve

DO provide a method to serve the app with
panel serve

if 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 --dev
DON'T serve with
python path_to_this_file.py
.
if 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.py
启动应用。

Best 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
tests
folder:
python
undefined
使用Panel,您可以轻松创建测试来验证用户行为,而无需编写客户端测试。
务必在
tests
文件夹中创建单独的测试:
python
undefined

DO 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.py
DO 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
    param.Parameterized
    or
    pn.viewable.Viewer
    classes to organize and manage state
  • DO create widgets with
    .from_param()
    method. DON'T do this for panes, i.e. pn.pane.Str has no from_param method.
  • DO use
    @param.depends()
    for reactive methods
  • DO use
    @param.depends(..., watch=True)
    to update parameter/ state values and for side-effects like sending an email.
  • DO group related parameters in separate
    Parameterized
    or
    Viewable
    classes
python
undefined
  • 请使用
    param.Parameterized
    pn.viewable.Viewer
    类来组织和管理状态
  • 请使用
    .from_param()
    方法创建小部件。不要为窗格(pane)使用此方法,例如pn.pane.Str没有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)
undefined

Create 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._layout
class 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 content
class 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.Select
    ,
    pn.widgets.DateRangeSlider
    and other widgets for input
  • pn.widgets.Tabulator
    to display tabular data like DataFrames
使用以下组件:
  • pn.widgets.IntSlider
    pn.widgets.Select
    pn.widgets.DateRangeSlider
    等小部件用于输入
  • pn.widgets.Tabulator
    用于显示表格数据(如DataFrames)

Panes

窗格

Use panes to display data:
  • pn.pane.Markdown
    and
    pn.pane.HTML
    to display strings
  • pn.pane.HoloViews
    ,
    pn.pane.Plotly
    ,
    pn.pane.Matplotlib
    or
    pn.pane.ECharts
    to display plots
使用窗格来显示数据:
  • 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.Tabs
    ,
    pn.Accordion
    for layouts
使用布局来排列组件:
  • 请使用
    pn.Column
    pn.Row
    pn.Tabs
    pn.Accordion
    进行布局

Templates

模板

  • DO use
    pn.template.FastListTemplate
    or other templates for served apps:
python
template = pn.template.FastListTemplate(
    title="Hello World App",
    sidebar=[instance._inputs],
    main=[instance._outputs],
    main_layout=None,
)
  • In the
    sidebar
    , DO use the order: 1) optional logo, 2) description, 3) input widgets, 4) documentation
  • In the
    sidebar
    , DO make sure components stretch width.
  • Do set
    main_layout=None
    for a modern styling.
  • 请为要部署的应用使用
    pn.template.FastListTemplate
    或其他模板:
python
template = pn.template.FastListTemplate(
    title="Hello World App",
    sidebar=[instance._inputs],
    main=[instance._outputs],
    main_layout=None,
)
  • sidebar
    中,请按照以下顺序排列:1) 可选Logo,2) 描述,3) 输入小部件,4) 文档
  • sidebar
    中,请确保组件宽度自适应。
  • 请设置
    main_layout=None
    以使用现代样式。

Responsive Design

响应式设计

  • DO use
    sizing_mode="stretch_width"
    by default:
python
with pn.config.set(sizing_mode="stretch_width"):
    character_input = pn.widgets...
    output_pane = pn.pane....
  • DO use
    FlexBox
    ,
    GridSpec
    or
    GridBox
    for complex, responsive grid layouts
  • DO set appropriate
    min_width
    ,
    min_height
    ,
    max_width
    and
    max_height
    to prevent layout collapse
  • 请默认使用
    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
pn.extension
to make sure their Javascript is loaded:
python
undefined
请记得将"tabulator"、"plotly"等扩展添加到
pn.extension
中,以确保其Javascript已加载:
python
undefined

✅ Good

✅ 正确

pn.extension("tabulator", "plotly")

DON'T add "bokeh". It's already loaded:

```python
pn.extension("tabulator", "plotly")

不要添加"bokeh",它已自动加载:

```python

❌ Bad

❌ 错误

pn.extension("bokeh")
undefined
pn.extension("bokeh")
undefined

Servable()

Servable()

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.
python
undefined
请将主组件标记为
.servable()
以将其包含在部署的应用中,并使用
pn.state.served
在应用通过
panel serve
启动时运行主方法。
python
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()
undefined
if name == "main": main().show()
undefined

Performance 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
    @pn.cache
    decorator for expensive computations
  • 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
    @pn.io.profiler
    to identify bottlenecks
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:
    search
    (documentation),
    pn_search
    (find components),
    pn_get
    (component details),
    pn_params
    (parameter info),
    ref_get
    (reference guides),
    skill_get
    (best-practice skills).
  • If MCP tools are not available but the
    holoviz-mcp
    CLI is installed (also available as
    hv
    ), use the equivalent CLI commands:
    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
    Tabulator
    related information via https://panel.holoviz.org/search.html?q=Tabulator url.
  • 如果HoloViz MCP服务器工具可用,请使用它们来查找文档和组件详情。有用的工具:
    search
    (文档)、
    pn_search
    (查找组件)、
    pn_get
    (组件详情)、
    pn_params
    (参数信息)、
    ref_get
    (参考指南)、
    skill_get
    (最佳实践技能)。
  • 如果MCP工具不可用但已安装
    holoviz-mcp
    CLI(也可通过
    hv
    访问),请使用等效的CLI命令:
    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
tests
folder and run them with
pytest 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
请在
tests
文件夹中添加测试,并使用
pytest 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
panel serve path_to_file.py --dev --show
with hot reload while developing!
  • Due to
    --show
    flag, a browser tab will automatically open showing your app.
  • Due to
    --dev
    flag, the panel server and app will automatically reload if you change the code.
  • 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
    --port {port-number}
    flag.
  • DO remind the user to test the application on multiple screen sizes (desktop, tablet, mobile)
  • DON'T use legacy
    --autoreload
    flag
  • DON't run
    python path_to_file.py
    to test or serve the app.
  • If you close the server to run other commands DO remember to restart it.
开发过程中,请始终启动并运行开发服务器
panel serve path_to_file.py --dev --show
以实现热重载!
  • 由于
    --show
    标志,浏览器标签页将自动打开并显示您的应用。
  • 由于
    --dev
    标志,Panel服务器和应用将在您修改代码后自动重载。
  • 应用将部署在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
undefined
python
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
undefined
widget = pn.widgets.Select(options=['A', 'B'], value='A') widget.link(self, value='model_type') # 难以推理
undefined

Reactive Updates Pattern

响应式更新模式

python
undefined
python
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 pane
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)  # 仅返回绘图,不返回窗格

✅ 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)
undefined

Static Components Pattern

静态组件模式

python
undefined
python
undefined

DO: 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}"
undefined
def _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}"
undefined

Other Guidance

其他指南

CheckButtonGroup

CheckButtonGroup

  • DO arrange vertically when displaying
    CheckButtonGroup
    in a sidebar
    CheckButtonGroup(..., vertical=True)
    .
  • DO set
    button_type="primary"
    and
    button_style="outline"
    .
  • 在侧边栏中显示
    CheckButtonGroup
    时,请垂直排列:
    CheckButtonGroup(..., vertical=True)
  • 请设置
    button_type="primary"
    button_style="outline"

Tabulator

Tabulator

  • DO set
    Tabulator.disabled=True
    unless you would like the user to be able to edit the table.
  • 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
    Markdown.disable_anchors=True
    to avoid page flickr when hovering over headers.
  • 请设置
    Markdown.disable_anchors=True
    以避免悬停在标题上时页面闪烁。

Bind

Bind

  • DON't bind a function to nothing:
    pn.bind(some_func)
    . Just run the function instead
    some_func()
    .
  • 不要将函数绑定到空值:
    pn.bind(some_func)
    。直接运行函数
    some_func()
    即可。

Plotting

绘图

  • DO use bar charts over pie Charts.
  • 请使用柱状图而非饼图。

HoloViews/hvPlot

HoloViews/hvPlot

DO follow the relevant
hvplot
,
holoviews
and
panel-holoviews
skills/ best practice guides!
请遵循相关的
hvplot
holoviews
panel-holoviews
技能/最佳实践指南!

Matplotlib

Matplotlib

CRITICAL: On windows set non-interactive backend before importing matplotlib.pyplot:
Why: The
'agg'
backend is required for server-side rendering without display. Panel needs to render plots as images, not interactive GUI windows.
Extension: DON'T include
'matplotlib'
in
pn.extension()
- it doesn't require JavaScript loading like Plotly or Bokeh extensions.
python
undefined
关键提示:在Windows系统上,在导入matplotlib.pyplot之前设置非交互式后端:
原因
'agg'
后端是无显示的服务器端渲染所必需的。Panel需要将绘图渲染为图像,而非交互式GUI窗口。
扩展:不要在
pn.extension()
中包含
'matplotlib'
- 它不需要像Plotly或Bokeh扩展那样加载JavaScript。
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 instead
pn.extension('matplotlib') # 不是Panel扩展

**最佳实践**:

- 请使用matplotlib创建出版物级别的静态绘图
- 渲染后关闭图形以防止内存泄漏:`plt.close(fig)`
- 对于交互式绘图,请考虑使用hvPlot或Plotly替代

Plotly

Plotly

Do set the template (theme) depending on the
theme
of the app.
python
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
请根据应用的
theme
设置模板(主题)。
python
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 fig

ECharts

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**:
```python
option = { "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:
```python
option = { "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默认使用合并策略。这会导致在移除系列时旧系列仍然存在。
当系列数量可能变化时,请使用
replaceMerge
选项
python
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** - 旧系列会残留:
```python

If 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
Timestamp
:
python
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系列进行比较时,请转换为
Timestamp
python
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