Shiny Express应用中Plotly折线图空白问题排查
Shiny Express Plotly折线图空白问题解决办法
问题现象
开发的Shiny Express应用中,过滤器、信息卡片及过滤后的DataFrame均能正常响应,但Plotly折线图显示空白,x轴(tran_date)显示默认的2000年日期。尝试多种日期转换方式后问题仍未解决。
核心原因
- 日期类型不匹配:
input.date()返回Pythondatetime.date对象,而原始DataFrame的tran_date是字符串类型,导致fee_df()中的日期过滤逻辑失效,返回空DataFrame,最终图表无数据显示。 - 日期转换时机错误:在
render_widget的feelp()函数中才转换tran_date类型,但过滤操作已在fee_df()中完成,此时转换无法修正过滤逻辑的问题。
修复方案
步骤1:提前转换原始DataFrame的日期类型
在创建df2后,立即将tran_date转换为与input.date()返回值匹配的date类型:
df2 = pd.DataFrame(data, columns=columns) df2['tran_date'] = pd.to_datetime(df2['tran_date']).dt.date
步骤2:调整fee_df()的过滤逻辑
确保日期比较时类型一致,同时简化Card_Type的匹配逻辑(避免不必要的模糊匹配):
@reactive.calc def fee_df(): drange = input.date() start_date, end_date = drange[0], drange[1] filtered_df = df2[ (df2['tran_date'] >= start_date) & (df2['tran_date'] <= end_date) ] inacct = input.acct() filtered_df = filtered_df[filtered_df['Card_Type'] == inacct] return filtered_df
步骤3:简化feelp()中的日期处理
由于fee_df()返回的DataFrame已包含正确的日期类型,无需再次转换:
@render_widget def feelp(): df = fee_df().copy() df = df.dropna(subset=['tran_date']) fig = px.line( data_frame = df, x = 'tran_date', y = 'fee_pct', color = 'Entity', color_discrete_map = {'1': 'blue', '3': 'red'}, line_shape = 'spline', markers = True, labels = { "tran_date": "Transaction Date", "fee_pct": "Fee Percentage" }, custom_data = 'Entity' ) fig.update_traces( line = dict(width = 3), hovertemplate = 'Entity %{customdata[0]}<br>' + '%{y:,.2f}%<br>' + '<extra></extra>' ) fig.update_layout( xaxis = dict(type='date'), hovermode = 'x unified', font=dict(family="Calibri Black",size=18), hoverlabel=dict(font_size=18, font_weight='bold') ) return fig
完整修复后代码
import plotly.express as px import pandas as pd import datetime as dt from shiny import reactive from shiny.express import ui, input, render from shinywidgets import render_widget import plotly columns = ["tran_date", "Card_Type", "Entity", "tran_amt", "fee", "fee_pct"] data = [ ["2025-04-10", "Test", "HF", 39.77, -1.39, 3.5], ["2025-04-10", "Test2", "1", 156722.44, -2133.43, 1.36], ["2025-04-10", "Test2", "3", 7026.4, -91.81, 1.31], ["2025-04-10", "Test2", "HF", 19326.39, -257.04, 1.33], ["2025-04-10", "Test3", "1", 537720.57, -17150.55, 3.19], ["2025-04-10", "Test3", "3", 51286.76, -1616.46, 3.15], ["2025-04-10", "Test3", "HF", 3610.66, -121.13, 3.35], ["2025-04-10", "Test4", "1", 769.76, -21.17, 2.75], ["2025-04-10", "Test4", "HF", 398.01, -5.77, 1.45], ["2025-04-10", "Test5", "1", 456447.03, -5058.61, 1.11], ["2025-04-10", "Test5", "3", 27686.38, -325.47, 1.18], ["2025-04-10", "Test5", "HF", 37173.72, -439.91, 1.18], ["2025-04-10", "Test6", "1", 1005795.05, -23607.7, 2.35], ["2025-04-10", "Test6", "3", 301824.57, -7086.06, 2.35], ["2025-04-10", "Test6", "HF", 24651.23, -579.0, 2.35] ] df2 = pd.DataFrame(data, columns = columns) # 提前转换tran_date为date类型,匹配input.date()返回值 df2['tran_date'] = pd.to_datetime(df2['tran_date']).dt.date # Define filter elements accts = df2['Card_Type'].unique().tolist() mindate = df2['tran_date'].min() maxdate = df2['tran_date'].max() # Define the UI ui.page_opts(title='Settlement Fee Tracking', fillable = True) with ui.sidebar(title='Filter Controls'): ui.input_select( 'acct', 'Select an Account', choices=accts ) ui.input_date_range( "date", "Date Range", min=mindate, max=maxdate, start=mindate, end=maxdate ) with ui.navset_pill(id = "tab"): with ui.nav_panel("Fee Percentage by Card Type - Entity Split"): # Graph with ui.layout_columns(): with ui.card(full_screen = True): ui.card_header("Card Fee Percentage Tracking") @render_widget def feelp(): df = fee_df().copy() df = df.dropna(subset=['tran_date']) fig = px.line( data_frame = df, x = 'tran_date', y = 'fee_pct', color = 'Entity', color_discrete_map = {'1': 'blue', '3': 'red'}, line_shape = 'spline', markers = True, labels = { "tran_date": "Transaction Date", "fee_pct": "Fee Percentage" }, custom_data = 'Entity' ) fig.update_traces( line = dict(width = 3), hovertemplate = 'Entity %{customdata[0]}<br>' + '%{y:,.2f}%<br>' + '<extra></extra>' ) fig.update_layout( xaxis = dict(type='date'), hovermode = 'x unified', font=dict(family="Calibri Black",size=18), hoverlabel=dict(font_size=18, font_weight='bold') ) return fig @reactive.calc def fee_df(): drange = input.date() start_date, end_date = drange[0], drange[1] # 日期过滤:类型一致,逻辑生效 filtered_df = df2[ (df2['tran_date'] >= start_date) & (df2['tran_date'] <= end_date) ] inacct = input.acct() # 直接匹配Card_Type,避免模糊匹配导致的意外过滤 filtered_df = filtered_df[filtered_df['Card_Type'] == inacct] return filtered_df
验证说明
修改后,过滤器将正确筛选出符合条件的数据,Plotly折线图将正常显示对应实体的费率趋势,x轴将显示正确的交易日期(如2025-04-10),不再出现空白或默认2000年日期的问题。
内容的提问来源于stack exchange,提问作者user30601489
相关产品推荐
相关产品推荐

