如何在Dash中展示含多Axes操作的Matplotlib径向图表
在Dash中展示Matplotlib径向图表的解决方案
你原代码的核心问题是:dcc.Graph组件要求传入Plotly格式的figure对象,而非Matplotlib的绘图艺术家对象(如plt.scatter返回的PathCollection)。针对包含多图层的径向图,提供两种可行实现方案:
方法1:将Matplotlib图表转为Base64图片嵌入Dash
可直接复用你已有的Matplotlib绘图代码,只需将生成的图表转为图片格式,通过html.Img组件展示。
完整代码示例
import dash from dash import dcc, html, Input, Output import numpy as np import pandas as pd import random import matplotlib.pyplot as plt import base64 from io import BytesIO app = dash.Dash(__name__) app.layout = html.Div(children=[ html.H1(children='径向血压数据可视化'), dcc.Slider( id='data_count', min=36, max=72, step=6, value=72, marks={i: str(i) for i in range(36, 73, 6)} ), html.Img(id='radial_plot') ]) @app.callback( Output('radial_plot', 'src'), Input('data_count', 'value') ) def update_radial_plot(data_count): # 生成合成数据 df = pd.DataFrame({'DATE_TIME': pd.date_range('2022-11-01', '2022-11-01 23:59:00', freq='20min')[:data_count], 'ID': [random.randrange(1, 2) for n in range(data_count)]}) df['HOUR'] = df['DATE_TIME'].dt.hour df['MINUTE'] = df['DATE_TIME'].dt.minute df['DBP'] = np.random.uniform(50, 80, size=data_count) df['DBP2'] = np.random.uniform(45, 75, size=data_count) df['SBP'] = np.random.uniform(110, 180, size=data_count) df['SBP2'] = np.random.uniform(90, 130, size=data_count) df = df[(df.ID==1)] df['seconds'] = df['DATE_TIME'].dt.hour*60 + df['DATE_TIME'].dt.minute # 绘制径向图 hour_minute = np.linspace(0, 2*np.pi, 24*60, endpoint=False) r = df.SBP.tolist() r2 = df.SBP2.tolist() s = df.DBP.tolist() s2 = df.DBP2.tolist() fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(8,8)) ax.plot(hour_minute[df['seconds']], r, color='red', label='SBP') ax.plot(hour_minute[df['seconds']], r2, color='red', label='SBP2') ax.plot(hour_minute[df['seconds']], s, color='blue', label='DBP') ax.plot(hour_minute[df['seconds']], s2, color='blue', label='DBP2') ax.fill_between(hour_minute[df['seconds']], r, r2, interpolate=True, color='red', alpha=0.3) ax.fill_between(hour_minute[df['seconds']], s, s2, interpolate=True, color='blue', alpha=0.3) ax.set_xticks(np.linspace(0, 2*np.pi, 24, endpoint=False)) ax.set_xticklabels(range(24)) ax.legend(loc='best', bbox_to_anchor=(1.0, 1.1), fontsize=8) ax.set_theta_direction(-1) ax.set_ylim(0, 180) ax.set_theta_offset(np.pi/2.0) # 转换为Base64图片 buffer = BytesIO() fig.savefig(buffer, format='png', bbox_inches='tight') buffer.seek(0) img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') plt.close(fig) # 释放内存 return f'data:image/png;base64,{img_base64}' if __name__ == '__main__': app.run_server(debug=True)
关键注意事项
- 用
BytesIO和base64将Matplotlib的fig对象转为可嵌入的图片字符串 - 必须调用
plt.close(fig)避免内存泄漏 bbox_inches='tight'确保图例等元素不会被截断
方法2:改用Plotly直接绘制径向图(Dash原生支持)
Dash基于Plotly构建,直接使用Plotly绘制极坐标图可获得更好的交互性(缩放、悬停提示等),无需依赖Matplotlib。
完整代码示例
import dash from dash import dcc, html, Input, Output, callback import numpy as np import pandas as pd import random import plotly.graph_objects as go app = dash.Dash(__name__) app.layout = html.Div(children=[ html.H1(children='径向血压数据可视化'), dcc.Slider( id='data_count', min=36, max=72, step=6, value=72, marks={i: str(i) for i in range(36, 73, 6)} ), dcc.Graph(id='radial_plot') ]) @callback( Output('radial_plot', 'figure'), Input('data_count', 'value') ) def update_radial_plot(data_count): # 生成合成数据 df = pd.DataFrame({'DATE_TIME': pd.date_range('2022-11-01', '2022-11-01 23:59:00', freq='20min')[:data_count], 'ID': [random.randrange(1, 2) for n in range(data_count)]}) df['HOUR'] = df['DATE_TIME'].dt.hour df['MINUTE'] = df['DATE_TIME'].dt.minute df['DBP'] = np.random.uniform(50, 80, size=data_count) df['DBP2'] = np.random.uniform(45, 75, size=data_count) df['SBP'] = np.random.uniform(110, 180, size=data_count) df['SBP2'] = np.random.uniform(90, 130, size=data_count) df = df[(df.ID==1)] # 转换为极坐标角度(0对应顶部,顺时针) df['theta'] = (df['HOUR']*60 + df['MINUTE']) / (24*60) * 2 * np.pi # 构建Plotly figure fig = go.Figure() # 添加SBP系列线条 fig.add_trace(go.Scatterpolar( r=df['SBP'], theta=df['theta'], mode='lines', name='SBP', line_color='red' )) fig.add_trace(go.Scatterpolar( r=df['SBP2'], theta=df['theta'], mode='lines', name='SBP2', line_color='red' )) # 添加DBP系列线条 fig.add_trace(go.Scatterpolar( r=df['DBP'], theta=df['theta'], mode='lines', name='DBP', line_color='blue' )) fig.add_trace(go.Scatterpolar( r=df['DBP2'], theta=df['theta'], mode='lines', name='DBP2', line_color='blue' )) # 添加填充区域 fig.add_trace(go.Scatterpolar( r=pd.concat([df['SBP'], df['SBP2'][::-1]]), theta=pd.concat([df['theta'], df['theta'][::-1]]), fill='toself', fillcolor='rgba(255,0,0,0.3)', line_color='rgba(255,255,255,0)', showlegend=False )) fig.add_trace(go.Scatterpolar( r=pd.concat([df['DBP'], df['DBP2'][::-1]]), theta=pd.concat([df['theta'], df['theta'][::-1]]), fill='toself', fillcolor='rgba(0,0,255,0.3)', line_color='rgba(255,255,255,0)', showlegend=False )) # 配置布局 fig.update_layout( polar=dict( radialaxis=dict(range=[0, 180]), angularaxis=dict( tickmode='array', tickvals=np.linspace(0, 2*np.pi, 24, endpoint=False), ticktext=[str(i) for i in range(24)], direction='clockwise' ) ), legend=dict(x=1.1, y=1.1), width=800, height=800 ) return fig if __name__ == '__main__': app.run_server(debug=True)
关键注意事项
- 用
Scatterpolar组件绘制极坐标线条 - 通过
fill='toself'实现填充区域,需将两条线的点首尾连接形成闭合路径 - 原生支持交互功能,无需额外配置即可实现数据悬停查看
内容的提问来源于stack exchange,提问作者dsapprentice
相关产品推荐
相关产品推荐

