You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击Sunburst Chart扇段无响应,如何触发on_click/on_hover回调?

问题:Plotly Sunburst图表无法触发on_click/on_hover回调实现筛选

我之前实现了点击柱状图扇段筛选表格的功能,现在想给Sunburst Chart做同样的筛选,但绑定的on_hover和on_click回调完全没反应。

现象

  • 初始化时trace.on_hover()和trace.on_click()会触发一次,但后续交互(点击/悬停)完全没响应
  • setHoverValues()和setClickValues()内部的断点从未触发
  • 实现逻辑和之前的柱状图完全一致,但Sunburst就是不响应

当前实现步骤

  1. 在server函数中创建sunburst_chart(),遍历图表的trace(Sunburst只有一个trace)
  2. 为trace绑定on_hover和on_click回调,分别调用setHoverValues和setClickValues
  3. 回调函数把points属性存入响应式变量
  4. 用文本组件显示响应式变量的值

完整代码

from shiny import App, reactive, render, ui
from shinywidgets import output_widget, render_widget
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd

df=pd.DataFrame({'Category':['Utilities','Rent','Insurance','Gas','Parking','Maintenance','Groceries','Restaurants'],
                 'Category_L2':['Home','Home','Home','Car','Car','Car','Food','Food']})

app_ui = ui.page_fluid(
    ui.output_text_verbatim('hoverInfoOutput'),
    ui.output_text_verbatim('clickInfoOutput'),
    output_widget('sunburst_chart'),
    )


def server(input, output, session):

    hoverInfo = reactive.value({})
    clickInfo = reactive.value({})

    def setHoverValues(trace, points, selector):
        if not points.point_inds:
            return
        hoverInfo.set(points.copy())

    @render.text
    def hoverInfoOutput():
        return hoverInfo.get()

    def setClickValues(trace, points, selector):
        if not points.point_inds:
            return
        clickInfo.set(points.copy())

    @render.text
    def clickInfoOutput():
        return hoverInfo.get()

    @render_widget
    def sunburst_chart():
        # Prepare data for the Sunburst chart
        labels = []
        parents = []
        values = []
        ids = []

        # Add Category_L2 (inner ring) data
        l2_counts = df['Category_L2'].value_counts()
        for l2_category, count in l2_counts.items():
            labels.append(l2_category)
            parents.append("")
            values.append(count)
            ids.append(f"L2_{l2_category}")

        # Add Category (outer ring) data
        for index, row in df.iterrows():
            category = row['Category']
            category_l2 = row['Category_L2']
            labels.append(category)
            parents.append(f"L2_{l2_category}")
            values.append(1)
            ids.append(f"Cat_{category}")

        # Create Sunburst chart
        fig = go.Figure(go.Sunburst(
            ids=ids,
            labels=labels,
            parents=parents,
            values=values,
            branchvalues="total",
            hovertemplate='<b>%{label}</b><br>Count: %{value}<extra></extra>',
        ))

        fig.update_layout(
            title_text='Two-Level Donut Chart: Category vs. Category_L2',
            margin=dict(t=50, l=0, r=0, b=0),
            uniformtext_minsize=10, uniformtext_mode='hide'
        )        
        figWidget = go.FigureWidget(fig)

        for trace in fig.data:
            trace.on_hover(setHoverValues)
            trace.on_click(setClickValues)
        return figWidget
    
app = App(app_ui, server)

解决方案:直接创建FigureWidget并绑定事件

问题根源是:从普通go.Figure转换为FigureWidget时,Sunburst trace的事件绑定上下文会丢失,导致交互无法触发回调。直接创建FigureWidget即可解决。

修改后的核心代码

@render_widget
def sunburst_chart():
    # 数据准备部分保持不变
    labels = []
    parents = []
    values = []
    ids = []

    l2_counts = df['Category_L2'].value_counts()
    for l2_category, count in l2_counts.items():
        labels.append(l2_category)
        parents.append("")
        values.append(count)
        ids.append(f"L2_{l2_category}")

    for index, row in df.iterrows():
        category = row['Category']
        category_l2 = row['Category_L2']
        labels.append(category)
        parents.append(f"L2_{l2_category}")
        values.append(1)
        ids.append(f"Cat_{category}")

    # 直接创建FigureWidget,而非先创建Figure再转换
    figWidget = go.FigureWidget(go.Sunburst(
        ids=ids,
        labels=labels,
        parents=parents,
        values=values,
        branchvalues="total",
        hovertemplate='<b>%{label}</b><br>Count: %{value}<extra></extra>',
    ))

    figWidget.update_layout(
        title_text='Two-Level Donut Chart: Category vs. Category_L2',
        margin=dict(t=50, l=0, r=0, b=0),
        uniformtext_minsize=10, uniformtext_mode='hide'
    )        

    # 直接给FigureWidget里的trace绑定事件
    for trace in figWidget.data:
        trace.on_hover(setHoverValues)
        trace.on_click(setClickValues)
    return figWidget

额外修复:纠正clickInfoOutput的逻辑错误

之前clickInfoOutput错误返回了hoverInfo的值,修改为:

@render.text
def clickInfoOutput():
    return clickInfo.get()

原理说明

FigureWidget是Plotly交互式图表的核心载体,直接创建它能确保事件绑定正确关联到交互式实例,避免转换过程中丢失事件上下文。


内容的提问来源于stack exchange,提问作者Dave Guenther

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 15:53:18