holoviews

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

HoloViews Development Skills

HoloViews 开发技巧

This document provides best practices for developing plots and charts with HoloViz HoloViews in notebooks and .py files.
Please develop as an Expert Python Developer developing advanced data-driven, analytics and testable data visualisations, dashboards and applications would do. Keep the code short, concise, documented, testable and professional.
本文档提供了在笔记本和.py文件中使用HoloViz HoloViews开发图表的最佳实践。
请以专业Python开发者的视角进行开发,遵循开发高级数据驱动、可分析、可测试的数据可视化、仪表盘及应用的标准。代码需简洁、规范、带注释、可测试且专业。

Dependencies

依赖

Core dependencies provided with the
holoviews
Python package:
  • 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. hvPlot is built upon holoviews.
  • colorcet: Perceptually uniform colormaps
  • panel: Provides widgets and layouts enabling tool, dashboard and data app development.
  • param: A declarative approach to creating classes with typed, validated, and documented parameters. Fundamental to the reactive programming model of hvPlot and the rest of the HoloViz ecosystem.
  • 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.
Optional dependencies from the HoloViz Ecosystem:
  • hvplot: Easy to use plotting library with Pandas
    .plot
    like API. Built on top of HoloViews.
  • 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.
  • holoviz-mcp: Model Context Protocol server for HoloViz ecosystem. Provides access to detailed documentation, component search and agent skills.
  • hvsampledata: Shared datasets for the HoloViz projects.
holoviews
Python包提供的核心依赖:
  • holoviews: 支持可组合元素的声明式数据可视化库。最适用于:复杂多层图表、高级交互(联动刷选、选择)、需要精细控制图表组合的场景、科学可视化。相比hvPlot功能更强大,但学习曲线更陡。hvPlot基于holoviews构建。
  • colorcet: 感知均匀的颜色映射库
  • panel: 提供组件和布局,支持工具、仪表盘和数据应用开发。
  • param: 用于创建带类型验证和文档化参数的类的声明式方法。是hvPlot及整个HoloViz生态响应式编程模型的基础。
  • pandas: 业界标准的表格数据DataFrame库。最适用于:数据清洗、转换、时间序列分析、可放入内存的数据集。是大多数数据处理工作的默认选择。
HoloViz生态的可选依赖:
  • hvplot: 易用的绘图库,具有类似Pandas
    .plot
    的API。基于HoloViews构建。
  • datashader: 将大型数据集(百万+数据点)渲染为图像以实现可视化。最适用于:大数据可视化、地理空间数据集、含数百万点的散点图、密集数据的热力图。需要hvPlot或HoloViews作为前端。
  • geoviews: 支持地图投影和瓦片数据源的地理数据可视化库。最适用于:地理/地理空间图表、基于地图的仪表盘、需要坐标系和投影的场景。基于HoloViews构建,可与hvPlot无缝协作。
  • holoviz-mcp: 为HoloViz生态提供的模型上下文协议服务器。提供详细文档、组件搜索和技能调用功能。
  • hvsampledata: HoloViz项目的共享数据集。

Installation for Development

开发环境安装

bash
pip install holoviews hvsampledata panel watchfiles
For development in .py files DO always include watchfiles for Panel hotreload.
bash
pip install holoviews hvsampledata panel watchfiles
在.py文件中开发时,务必包含watchfiles以支持Panel热重载。

Earthquake Sample Data

地震示例数据

In the example below we will use the
earthquakes
sample data:
python
import hvsampledata

hvsampledata.earthquakes("pandas")
text
Tabular record of earthquake events from the USGS Earthquake Catalog that provides detailed
information including parameters such as time, location as latitude/longitude coordinates
and place name, depth, and magnitude. The dataset contains 596 events.

Note: The columns `depth_class` and `mag_class` were created by categorizing numerical values from
the `depth` and `mag` columns in the original dataset using custom-defined binning:

Depth Classification

| depth     | depth_class  |
|-----------|--------------|
| Below 70  | Shallow      |
| 70 - 300  | Intermediate |
| Above 300 | Deep         |

Magnitude Classification

| mag         | mag_class |
|-------------|-----------|
| 3.9 - <4.9  | Light     |
| 4.9 - <5.9  | Moderate  |
| 5.9 - <6.9  | Strong    |
| 6.9 - <7.9  | Major     |


Schema
------
| name        | type       | description                                                         |
|:------------|:-----------|:--------------------------------------------------------------------|
| time        | datetime   | UTC Time when the event occurred.                                   |
| lat         | float      | Decimal degrees latitude. Negative values for southern latitudes.   |
| lon         | float      | Decimal degrees longitude. Negative values for western longitudes   |
| depth       | float      | Depth of the event in kilometers.                                   |
| depth_class | category   | The depth category derived from the depth column.                   |
| mag         | float      | The magnitude for the event.                                        |
| mag_class   | category   | The magnitude category derived from the mag column.                 |
| place       | string     | Textual description of named geographic region near to the event.   |
以下示例将使用
earthquakes
示例数据:
python
import hvsampledata

hvsampledata.earthquakes("pandas")
text
Tabular record of earthquake events from the USGS Earthquake Catalog that provides detailed
information including parameters such as time, location as latitude/longitude coordinates
and place name, depth, and magnitude. The dataset contains 596 events.

Note: The columns `depth_class` and `mag_class` were created by categorizing numerical values from
the `depth` and `mag` columns in the original dataset using custom-defined binning:

Depth Classification

| depth     | depth_class  |
|-----------|--------------|
| Below 70  | Shallow      |
| 70 - 300  | Intermediate |
| Above 300 | Deep         |

Magnitude Classification

| mag         | mag_class |
|-------------|-----------|
| 3.9 - <4.9  | Light     |
| 4.9 - <5.9  | Moderate  |
| 5.9 - <6.9  | Strong    |
| 6.9 - <7.9  | Major     |


Schema
------
| name        | type       | description                                                         |
|:------------|:-----------|:--------------------------------------------------------------------|
| time        | datetime   | UTC Time when the event occurred.                                   |
| lat         | float      | Decimal degrees latitude. Negative values for southern latitudes.   |
| lon         | float      | Decimal degrees longitude. Negative values for western longitudes   |
| depth       | float      | Depth of the event in kilometers.                                   |
| depth_class | category   | The depth category derived from the depth column.                   |
| mag         | float      | The magnitude for the event.                                        |
| mag_class   | category   | The magnitude category derived from the mag column.                 |
| place       | string     | Textual description of named geographic region near to the event.   |

Reference Data Exploration Example

参考数据探索示例

Below is a simple reference example for data exploration.
python
import hvsampledata
import holoviews as hv
以下是一个简单的数据探索参考示例。
python
import hvsampledata
import holoviews as hv

DO always run hv.extension() to load the HoloViews javascript extensions

DO always run hv.extension() to load the HoloViews javascript extensions

DO specify the backend you intend to use (e.g., "bokeh", "matplotlib", "plotly")

DO specify the backend you intend to use (e.g., "bokeh", "matplotlib", "plotly")

hv.extension("bokeh")
hv.extension("bokeh")

Do keep the extraction, transformation and plotting of data clearly separate

Do keep the extraction, transformation and plotting of data clearly separate

Extract: earthquakes sample data

Extract: earthquakes sample data

data = hvsampledata.earthquakes("pandas")
data = hvsampledata.earthquakes("pandas")

Transform: Group by mag_class and count occurrences

Transform: Group by mag_class and count occurrences

mag_class_counts = data.groupby('mag_class').size().reset_index(name='counts')
mag_class_counts = data.groupby('mag_class').size().reset_index(name='counts')

DO Specify an element type. Here its hv.Bars, i.e. a Bar plot.

DO Specify an element type. Here its hv.Bars, i.e. a Bar plot.

plot = hv.Bars( # DO provide the data explicitly data = mag_class_counts, # DO always specify the key dimensions (kdims) and value dimensions (vdims) as a single value or a list of values kdims='mag_class', vdims='counts' ).opts( # DO specify optional styling options using .opts() line_color=None, # DO specify optional plot options using .opts() title='Earthquake Counts by Magnitude Class' )
plot = hv.Bars( # DO provide the data explicitly data = mag_class_counts, # DO always specify the key dimensions (kdims) and value dimensions (vdims) as a single value or a list of values kdims='mag_class', vdims='counts' ).opts( # DO specify optional styling options using .opts() line_color=None, # DO specify optional plot options using .opts() title='Earthquake Counts by Magnitude Class' )

If working in notebook DO output to plot:

If working in notebook DO output to plot:

plot
plot

If working in .py file DO use panel:

If working in .py file DO use panel:

import panel as pn
import panel as pn

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

Instead provide pn.state.served check

Instead provide pn.state.served check

if pn.state.served: # DO always run pn.extension() to load panel javascript extensions pn.extension() # DO remember to add .servable to the panel components you want to serve with the app pn.panel(plot, sizing_mode="stretch_both").servable()

If working in a .py file DO serve the plot with hotreload:

```bash
panel serve path/to/file.py --dev --show
DONT serve with
python path_to_this_file.py
.
if pn.state.served: # DO always run pn.extension() to load panel javascript extensions pn.extension() # DO remember to add .servable to the panel components you want to serve with the app pn.panel(plot, sizing_mode="stretch_both").servable()

如果在.py文件中工作,请使用热重载启动图表服务:

```bash
panel serve path/to/file.py --dev --show
不要使用
python path_to_this_file.py
启动服务。

Reference Group By

参考分组示例

In this example we also groupby
depth_class
, i.e. a dropdown widget is added to select the
depth_class
to filter by.
python
import hvsampledata
import holoviews as hv

hv.extension("bokeh")

data = hvsampledata.earthquakes("pandas")

mag_class_counts = data.groupby(['mag_class', 'depth_class']).size().reset_index(name='counts')
print(mag_class_counts)

plot = hv.Bars(
    data = mag_class_counts,
    kdims=['mag_class','depth_class'],
    vdims='counts',
).groupby(
    "depth_class"
).opts(
    # DO specify optional styling options using .opts()
    line_color=None,
    # DO specify optional plot options using .opts()
    title='Earthquake Counts by Magnitude Class and Depth Class',
    width=800,
)
在本示例中,我们还将按
depth_class
分组,即添加一个下拉组件来选择要筛选的
depth_class
python
import hvsampledata
import holoviews as hv

hv.extension("bokeh")

data = hvsampledata.earthquakes("pandas")

mag_class_counts = data.groupby(['mag_class', 'depth_class']).size().reset_index(name='counts')
print(mag_class_counts)

plot = hv.Bars(
    data = mag_class_counts,
    kdims=['mag_class','depth_class'],
    vdims='counts',
).groupby(
    "depth_class"
).opts(
    # DO specify optional styling options using .opts()
    line_color=None,
    # DO specify optional plot options using .opts()
    title='Earthquake Counts by Magnitude Class and Depth Class',
    width=800,
)

If working in notebook DO output to plot:

If working in notebook DO output to plot:

plot
plot

If working in .py file DO use panel:

If working in .py file DO use panel:

import panel as pn
import panel as pn

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

Instead provide pn.state.served check

Instead provide pn.state.served check

if pn.state.served: # DO always run pn.extension() to load panel javascript extensions pn.extension() # DO remember to add .servable to the panel components you want to serve with the app pn.panel(plot, sizing_mode="stretch_both").servable()

If we add `.layout` the data will be visualized as 3 individual plots (one per depth_class):

```python
import hvsampledata
import holoviews as hv

hv.extension("bokeh")

data = hvsampledata.earthquakes("pandas")

mag_class_counts = data.groupby(['mag_class', 'depth_class']).size().reset_index(name='counts')
print(mag_class_counts)

plot = hv.Bars(
    data = mag_class_counts,
    kdims=['mag_class','depth_class'],
    vdims='counts',
).groupby(
    "depth_class"
).opts(
    # DO specify optional styling options using .opts()
    line_color=None,
    width=800,
).layout()
if pn.state.served: # DO always run pn.extension() to load panel javascript extensions pn.extension() # DO remember to add .servable to the panel components you want to serve with the app pn.panel(plot, sizing_mode="stretch_both").servable()

如果添加`.layout`,数据将被可视化成3个独立的图表(每个depth_class对应一个):

```python
import hvsampledata
import holoviews as hv

hv.extension("bokeh")

data = hvsampledata.earthquakes("pandas")

mag_class_counts = data.groupby(['mag_class', 'depth_class']).size().reset_index(name='counts')
print(mag_class_counts)

plot = hv.Bars(
    data = mag_class_counts,
    kdims=['mag_class','depth_class'],
    vdims='counts',
).groupby(
    "depth_class"
).opts(
    # DO specify optional styling options using .opts()
    line_color=None,
    width=800,
).layout()

If working in notebook DO output to plot:

If working in notebook DO output to plot:

plot
plot

If working in .py file DO use panel:

If working in .py file DO use panel:

import panel as pn
import panel as pn

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

Instead provide pn.state.served check

Instead provide pn.state.served check

if pn.state.served: # DO always run pn.extension() to load panel javascript extensions pn.extension() # DO remember to add .servable to the panel components you want to serve with the app pn.panel(plot, sizing_mode="stretch_both").servable()

If instead of `.layout()` we add `.overlay()`, one plot will be created, but the depth_class'es will be visualized by different colors.

```python
import hvsampledata
import holoviews as hv

hv.extension("bokeh")

data = hvsampledata.earthquakes("pandas")

mag_class_counts = data.groupby(['mag_class', 'depth_class']).size().reset_index(name='counts')
print(mag_class_counts)

plot = hv.Bars(
    data = mag_class_counts,
    kdims=['mag_class','depth_class'],
    vdims='counts',
).groupby(
    "depth_class"
).opts(
    # DO specify optional styling options using .opts()
    line_color=None,
    width=800,
).overlay()
if pn.state.served: # DO always run pn.extension() to load panel javascript extensions pn.extension() # DO remember to add .servable to the panel components you want to serve with the app pn.panel(plot, sizing_mode="stretch_both").servable()

如果我们不使用`.layout()`而是使用`.overlay()`,将生成一个图表,但不同的depth_class会用不同颜色展示。

```python
import hvsampledata
import holoviews as hv

hv.extension("bokeh")

data = hvsampledata.earthquakes("pandas")

mag_class_counts = data.groupby(['mag_class', 'depth_class']).size().reset_index(name='counts')
print(mag_class_counts)

plot = hv.Bars(
    data = mag_class_counts,
    kdims=['mag_class','depth_class'],
    vdims='counts',
).groupby(
    "depth_class"
).opts(
    # DO specify optional styling options using .opts()
    line_color=None,
    width=800,
).overlay()

If working in notebook DO output to plot:

If working in notebook DO output to plot:

plot
plot

If working in .py file DO use panel:

If working in .py file DO use panel:

import panel as pn
import panel as pn

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

Instead provide pn.state.served check

Instead provide pn.state.served check

if pn.state.served: # DO always run pn.extension() to load panel javascript extensions pn.extension() # DO remember to add .servable to the panel components you want to serve with the app pn.panel(plot, sizing_mode="stretch_both").servable()

Note: This works better for Curve or Scatter plots
if pn.state.served: # DO always run pn.extension() to load panel javascript extensions pn.extension() # DO remember to add .servable to the panel components you want to serve with the app pn.panel(plot, sizing_mode="stretch_both").servable()

注意:这种方式在Curve或Scatter图表中效果更好

Reference Publication Quality Bar Chart

参考出版级柱状图

python
undefined
python
undefined

============================================================================

============================================================================

Publication-Quality Bar Chart - HoloViews Best Practices Example

Publication-Quality Bar Chart - HoloViews Best Practices Example

============================================================================

============================================================================

Demonstrates:

Demonstrates:

- Data extraction, transformation, and visualization separation

- Data extraction, transformation, and visualization separation

- Custom Bokeh themes for consistent styling

- Custom Bokeh themes for consistent styling

- Interactive tooltips with formatted data

- Interactive tooltips with formatted data

- Text annotations on bars

- Text annotations on bars

- Professional fonts, grids, and axis formatting

- Professional fonts, grids, and axis formatting

- Panel integration for web serving

- Panel integration for web serving

============================================================================

============================================================================

import hvsampledata import panel as pn from bokeh.models.formatters import NumeralTickFormatter from bokeh.themes import Theme
import holoviews as hv from holoviews.plotting.bokeh import ElementPlot
import hvsampledata import panel as pn from bokeh.models.formatters import NumeralTickFormatter from bokeh.themes import Theme
import holoviews as hv from holoviews.plotting.bokeh import ElementPlot

============================================================================

============================================================================

BOKEH THEME SETUP - Define global styling

BOKEH THEME SETUP - Define global styling

============================================================================

============================================================================

ACCENT_COLOR = '#007ACC' # Professional blue FONT_FAMILY = 'Roboto'
def create_bokeh_theme(font_family=FONT_FAMILY, accent_color=ACCENT_COLOR): """Create custom theme with specified font. Default: Roboto""" return Theme(json={ 'attrs': { 'Title': { 'text_font': font_family, 'text_font_size': '16pt', 'text_font_style': 'bold' }, 'Axis': { 'axis_label_text_font': font_family, 'axis_label_text_font_size': '12pt', 'axis_label_text_font_style': 'bold', 'major_label_text_font': font_family, 'major_label_text_font_size': '10pt', 'major_tick_line_color': "black", # Remove tick marks 'minor_tick_line_color': None }, 'Plot': { 'background_fill_color': '#fafafa', 'border_fill_color': '#fafafa' }, 'Legend': { 'label_text_font': font_family, 'label_text_font_size': '10pt' }, 'Toolbar': { "autohide": True, "logo": None, "stylesheets": [ f""" .bk-OnOffButton.bk-active{{ border-color: {accent_color} !important; }} """ ] }, # Does not work via Theme, so added here for reference purposes until I figure out how to do it 'Tooltip': { "stylesheets": [f""" .bk-tooltip-row-label {{ color: {ACCENT_COLOR} !important; }}"""]
        }
    }
})
ACCENT_COLOR = '#007ACC' # Professional blue FONT_FAMILY = 'Roboto'
def create_bokeh_theme(font_family=FONT_FAMILY, accent_color=ACCENT_COLOR): """Create custom theme with specified font. Default: Roboto""" return Theme(json={ 'attrs': { 'Title': { 'text_font': font_family, 'text_font_size': '16pt', 'text_font_style': 'bold' }, 'Axis': { 'axis_label_text_font': font_family, 'axis_label_text_font_size': '12pt', 'axis_label_text_font_style': 'bold', 'major_label_text_font': font_family, 'major_label_text_font_size': '10pt', 'major_tick_line_color': "black", # Remove tick marks 'minor_tick_line_color': None }, 'Plot': { 'background_fill_color': '#fafafa', 'border_fill_color': '#fafafa' }, 'Legend': { 'label_text_font': font_family, 'label_text_font_size': '10pt' }, 'Toolbar': { "autohide": True, "logo": None, "stylesheets": [ f""" .bk-OnOffButton.bk-active{{ border-color: {accent_color} !important; }} """ ] }, # Does not work via Theme, so added here for reference purposes until I figure out how to do it 'Tooltip': { "stylesheets": [f""" .bk-tooltip-row-label {{ color: {ACCENT_COLOR} !important; }}"""]
        }
    }
})

Apply theme globally - affects all plots

Apply theme globally - affects all plots

hv.renderer('bokeh').theme = create_bokeh_theme()
hv.renderer('bokeh').theme = create_bokeh_theme()

============================================================================

============================================================================

HOLOVIEWS OPTS SETUP - Define global configuration

HOLOVIEWS OPTS SETUP - Define global configuration

============================================================================

============================================================================

GLOBAL_BACKEND_OPTS={ 'plot.xgrid.visible': False, # Only horizontal grid lines 'plot.ygrid.visible': True, 'plot.ygrid.grid_line_color': "black", 'plot.ygrid.grid_line_alpha': 0.1, 'plot.min_border_left': 80, # Add padding on left (for y-axis label) 'plot.min_border_bottom': 80, # Add padding on bottom (for x-axis label) 'plot.min_border_right': 30, # Add padding on right 'plot.min_border_top': 80, # Add padding on top }
ElementPlot.param.backend_opts.default = GLOBAL_BACKEND_OPTS ElementPlot.param.yformatter.default = NumeralTickFormatter(format='0a') # 1k, ...
hv.opts.defaults( hv.opts.Bars( color=ACCENT_COLOR, # Professional blue line_color=None, # Remove bar borders ), hv.opts.Labels( text_baseline='bottom', text_font_size='11pt', text_font_style='normal', text_color='#333333', ), ) hv.Cycle.default_cycles["default_colors"] = [ACCENT_COLOR, '#00948A', '#7E59BD', '#FFA20C', '#DA4341', '#D6F1FF', '#DAF5F4', '#F0E8FF', '#FFF8EA', '#FFF1EA', '#001142', '#003336', '#290031', '#371F00', '#3A0C13']
GLOBAL_BACKEND_OPTS={ 'plot.xgrid.visible': False, # Only horizontal grid lines 'plot.ygrid.visible': True, 'plot.ygrid.grid_line_color': "black", 'plot.ygrid.grid_line_alpha': 0.1, 'plot.min_border_left': 80, # Add padding on left (for y-axis label) 'plot.min_border_bottom': 80, # Add padding on bottom (for x-axis label) 'plot.min_border_right': 30, # Add padding on right 'plot.min_border_top': 80, # Add padding on top }
ElementPlot.param.backend_opts.default = GLOBAL_BACKEND_OPTS ElementPlot.param.yformatter.default = NumeralTickFormatter(format='0a') # 1k, ...
hv.opts.defaults( hv.opts.Bars( color=ACCENT_COLOR, # Professional blue line_color=None, # Remove bar borders ), hv.opts.Labels( text_baseline='bottom', text_font_size='11pt', text_font_style='normal', text_color='#333333', ), ) hv.Cycle.default_cycles["default_colors"] = [ACCENT_COLOR, '#00948A', '#7E59BD', '#FFA20C', '#DA4341', '#D6F1FF', '#DAF5F4', '#F0E8FF', '#FFF8EA', '#FFF1EA', '#001142', '#003336', '#290031', '#371F00', '#3A0C13']

============================================================================

============================================================================

DATA PIPELINE - Separate extraction, transformation, and plotting

DATA PIPELINE - Separate extraction, transformation, and plotting

============================================================================

============================================================================

def get_earthquake_data(): """Extract raw earthquake data from sample dataset""" return hvsampledata.earthquakes("pandas")
def aggregate_by_magnitude(earthquake_data): """Transform: Group earthquakes by magnitude class with statistics""" # Aggregate: count events and calculate average depth per magnitude class aggregated = ( earthquake_data .groupby('mag_class', observed=True) .agg({'mag': 'count', 'depth': 'mean'}) .reset_index() .rename(columns={'mag': 'event_count', 'depth': 'avg_depth'}) .sort_values('event_count', ascending=False) )
# Add percentage column for tooltips
aggregated['percentage'] = (
    aggregated['event_count'] / aggregated['event_count'].sum() * 100
)

return aggregated
def create_bar_chart(aggregated_data): """Create publication-quality bar chart with labels and tooltips""" default_tools=['save']
# Main bar chart with professional styling
bar_chart = hv.Bars(aggregated_data, kdims='mag_class', vdims=['event_count', 'percentage', 'avg_depth']).opts(
    # Titles and labels
    title='Earthquake Distribution by Magnitude',
    xlabel='Magnitude',
    ylabel='Number of Events',

    # Interactivity
    # hover_cols = ["mag_class", "event_count", "percentage", "avg_depth"],
    hover_tooltips=[
        ('Magnitude', '@mag_class'),
        ('Events', '@event_count{0,0}'),      # Format: 1,234
        ('Percentage', '@percentage{0 a}%'), # Format: 45%
        ('Avg Depth', '@avg_depth{0f} km')  # Format: 99 km
    ],
    default_tools=default_tools
)

# Add text labels above bars
labels_data = aggregated_data.copy()
labels_data['label_y'] = labels_data['event_count'] + 20  # Offset above bars

text_labels = hv.Labels(labels_data, kdims=['mag_class', 'label_y'], vdims=['event_count', 'percentage', 'avg_depth']).opts(
    hover_tooltips=[
        ('Magnitude', '@mag_class'),
        ('Events', '@event_count{0,0}'),      # Format: 1,234
        # tooltips below do currently not work on Labels
        # ('Percentage', '@percentage{0 a}%'), # Format: 45%
        # ('Avg Depth', '@avg_depth{0f} km'),  # Format: 99 km
    ],
    default_tools=default_tools
)

# Overlay: bar chart * text labels
return bar_chart * text_labels
def create_plot(): """Main function: Extract → Transform → Plot""" # Extract: Get raw data earthquake_data = get_earthquake_data()
# Transform: Aggregate and calculate statistics
aggregated = aggregate_by_magnitude(earthquake_data)

# Visualize: Create publication-quality chart
chart = create_bar_chart(aggregated)

return chart
def get_earthquake_data(): """Extract raw earthquake data from sample dataset""" return hvsampledata.earthquakes("pandas")
def aggregate_by_magnitude(earthquake_data): """Transform: Group earthquakes by magnitude class with statistics""" # Aggregate: count events and calculate average depth per magnitude class aggregated = ( earthquake_data .groupby('mag_class', observed=True) .agg({'mag': 'count', 'depth': 'mean'}) .reset_index() .rename(columns={'mag': 'event_count', 'depth': 'avg_depth'}) .sort_values('event_count', ascending=False) )
# Add percentage column for tooltips
aggregated['percentage'] = (
    aggregated['event_count'] / aggregated['event_count'].sum() * 100
)

return aggregated
def create_bar_chart(aggregated_data): """Create publication-quality bar chart with labels and tooltips""" default_tools=['save']
# Main bar chart with professional styling
bar_chart = hv.Bars(aggregated_data, kdims='mag_class', vdims=['event_count', 'percentage', 'avg_depth']).opts(
    # Titles and labels
    title='Earthquake Distribution by Magnitude',
    xlabel='Magnitude',
    ylabel='Number of Events',

    # Interactivity
    # hover_cols = ["mag_class", "event_count", "percentage", "avg_depth"],
    hover_tooltips=[
        ('Magnitude', '@mag_class'),
        ('Events', '@event_count{0,0}'),      # Format: 1,234
        ('Percentage', '@percentage{0 a}%'), # Format: 45%
        ('Avg Depth', '@avg_depth{0f} km')  # Format: 99 km
    ],
    default_tools=default_tools
)

# Add text labels above bars
labels_data = aggregated_data.copy()
labels_data['label_y'] = labels_data['event_count'] + 20  # Offset above bars

text_labels = hv.Labels(labels_data, kdims=['mag_class', 'label_y'], vdims=['event_count', 'percentage', 'avg_depth']).opts(
    hover_tooltips=[
        ('Magnitude', '@mag_class'),
        ('Events', '@event_count{0,0}'),      # Format: 1,234
        # tooltips below do currently not work on Labels
        # ('Percentage', '@percentage{0 a}%'), # Format: 45%
        # ('Avg Depth', '@avg_depth{0f} km'),  # Format: 99 km
    ],
    default_tools=default_tools
)

# Overlay: bar chart * text labels
return bar_chart * text_labels
def create_plot(): """Main function: Extract → Transform → Plot""" # Extract: Get raw data earthquake_data = get_earthquake_data()
# Transform: Aggregate and calculate statistics
aggregated = aggregate_by_magnitude(earthquake_data)

# Visualize: Create publication-quality chart
chart = create_bar_chart(aggregated)

return chart

============================================================================

============================================================================

PANEL APP SETUP

PANEL APP SETUP

============================================================================

============================================================================

Serve the chart when running with Panel

Serve the chart when running with Panel

if pn.state.served: # Load Panel JavaScript extensions pn.extension()
# Apply custom Bokeh theme (override the global theme)
# Create and serve the chart
plot = create_plot()
pn.panel(plot, sizing_mode="stretch_both", margin=25).servable()
undefined
if pn.state.served: # Load Panel JavaScript extensions pn.extension()
# Apply custom Bokeh theme (override the global theme)
# Create and serve the chart
plot = create_plot()
pn.panel(plot, sizing_mode="stretch_both", margin=25).servable()
undefined

General Instructions

通用指南

  • In a notebook always run
    hv.extension()
    to load any Javascript dependencies.
python
import holoviews as hv

hv.extension()
...
  • Prefer Bokeh > Plotly > Matplotlib plotting backend for interactivity
  • DO use bar charts over pie Charts. Pie charts are not supported.
  • DO use NumeralTickFormatter and 'a' formatter for easy axis formatting:
python
from bokeh.models.formatters import NumeralTickFormatter

plot.opts(
    yformatter=NumeralTickFormatter(format='0.00a'),  # Format as 1.00M, 2.50M, etc.
)
InputFormat StringOutput
1230974'0.0a'1.2m
1460'0 a'1 k
-104000'0a'-104k
  • 在笔记本中,务必运行
    hv.extension()
    以加载HoloViews的JavaScript依赖。
python
import holoviews as hv

hv.extension()
...
  • 为了实现交互性,优先选择Bokeh > Plotly > Matplotlib作为绘图后端
  • 优先使用柱状图而非饼图。HoloViews不支持饼图。
  • 推荐使用NumeralTickFormatter和'a'格式化器来简化轴格式化:
python
from bokeh.models.formatters import NumeralTickFormatter

plot.opts(
    yformatter=NumeralTickFormatter(format='0.00a'),  # Format as 1.00M, 2.50M, etc.
)
输入值格式字符串输出结果
1230974'0.0a'1.2m
1460'0 a'1 k
-104000'0a'-104k

Saving a plot

保存图表

You can save a plot to html with
hv.save
:
python
hv.save(some_plot, 'some_plot.html')
你可以使用
hv.save
将图表保存为HTML文件:
python
hv.save(some_plot, 'some_plot.html')

Recommended Plot Types

推荐图表类型

Curve - Line plots for time series and continuous data Scatter - Scatter plots for exploring relationships between variables Bars - Bar charts for categorical comparisons Histogram - Histograms for distribution analysis Area - Area plots for stacked or filled visualizations
Curve - 用于时间序列和连续数据的折线图 Scatter - 用于探索变量间关系的散点图 Bars - 用于分类比较的柱状图 Histogram - 用于分布分析的直方图 Area - 用于堆叠或填充可视化的面积图

Workflows

工作流程

Lookup additional information

查询更多信息

  • If the HoloViz MCP server tools are available, DO use them:
    search
    (documentation),
    hv_list
    (available elements),
    hv_get
    (docstrings and options),
    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 hv list
    ,
    holoviz-mcp hv get
    .
  • If neither is available, DO search the web. For example searching the HoloViews website for relevant information via https://holoviews.org url.
  • 如果HoloViz MCP服务器工具可用,请使用它们:
    search
    (文档查询)、
    hv_list
    (可用元素列表)、
    hv_get
    (文档字符串和选项)、
    skill_get
    (最佳实践技能)。
  • 如果MCP工具不可用但已安装
    holoviz-mcp
    CLI(也可通过
    hv
    命令调用),使用对应的CLI命令:
    holoviz-mcp search
    holoviz-mcp hv list
    holoviz-mcp hv get
  • 如果以上都不可用,请通过网络搜索。例如通过https://holoviews.org网站查找相关信息。

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 separate data extraction and transformation from plotting code.
  • DO fix any test errors and rerun the tests
  • DO run the tests and fix errors before displaying or serving the plots
请在
tests
文件夹中添加测试,并使用
pytest tests/path/to/test_file.py
运行测试。
  • 务必将数据提取、转换与绘图代码分离。
  • 修复所有测试错误并重新运行测试
  • 在展示或发布图表前,务必运行测试并修复所有错误

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 plot 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.
  • DO use
    pn.Column, pn.Tabs, pn.Accordion
    to layout multiple plots
  • 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
    来测试或启动应用。
  • 请使用
    pn.Column, pn.Tabs, pn.Accordion
    来布局多个图表
  • 如果关闭服务器执行其他命令,请记得重新启动服务器。