Plotly子图布局技巧与实战案例

笔记哥 / 04-19 / 46点赞 / 0评论 / 274阅读
在数据可视化领域,`Plotly`的**子图布局**是打造专业级仪表盘的核心武器。 当面对多维数据集时,合理的子图布局能显著提升多数据关联分析效率,让数据的呈现更加直观和美观。 本文将深入探讨`Plotly`中子图布局技巧,并结合代码实现与实际场景案例,介绍多子图组织方法的技巧。 # 多子图布局 ## 网格布局 **网格布局**是`Plotly`中实现多子图排列的一种常见方式,通过`make_subplots`函数,我们可以轻松创建行列对齐的子图。 例如,设置`rows=2, cols=3`,就可以生成一个**2行3列**的子图网格,这种方式的好处是子图的尺寸会自动分配, 而且我们还可以通过`horizontal_spacing`和`vertical_spacing`参数来调整子图之间的水平和垂直间距,从而让整个布局更加紧凑和美观。 ```csharp from plotly.subplots import make_subplots import plotly.graph_objects as go fig = make_subplots(rows=2, cols=3, horizontal_spacing=0.2, vertical_spacing=0.2) fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]), row=1, col=1) fig.add_trace(go.Scatter(x=[20, 30, 40], y=[50, 60, 70]), row=1, col=2) fig.add_trace(go.Scatter(x=[300, 400, 500], y=[600, 700, 800]), row=1, col=3) fig.add_trace(go.Scatter(x=[4000, 5000, 6000], y=[7000, 8000, 9000]), row=2, col=1) fig.add_trace( go.Scatter(x=[50000, 60000, 70000], y=[80000, 90000, 100000]), row=2, col=2 ) fig.add_trace( go.Scatter(x=[600000, 700000, 800000], y=[900000, 1000000, 1100000]), row=2, col=3 ) fig.show() ``` ![](https://cdn.res.knowhub.vip/c/2504/19/51eb8c75.png?G1IAAMTsdJzIi0Sl26hD2jvFHc0AQxJBpZr1es9Z%2byb6fmewxGe0Pn1%2f%2bL316VQzoMRghSD0ZDAVNUMKprXwVVDiGg4%3d) ## 自由布局 **自由布局**则给予了我们更大的灵活性,通过`domain`参数,我们可以手动设置子图的位置,即指定子图在图表中的`x`和`y`坐标范围。 这种方式非常适合实现一些非对齐排列的子图布局,比如主图与缩略图的组合。 我们可以将主图放在较大的区域,而将缩略图放在角落,通过这种方式来辅助展示数据的局部细节。 ```csharp # 自由布局 import plotly.graph_objects as go # 自由布局示例 fig_free = go.Figure() # 添加第一个子图 fig_free.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6], name="Trace 1")) # 添加第二个子图 fig_free.add_trace(go.Scatter(x=[20, 30, 40], y=[50, 60, 70], name="Trace 2")) # 更新布局,定义每个子图的domain fig_free.update_layout( xaxis=dict(domain=[0, 0.7]), # 第一个子图占据左侧70% yaxis=dict(domain=[0, 1]), # 第一个子图占据整个高度 xaxis2=dict(domain=[0.7, 1], anchor="y2"), # 第二个子图占据右侧30% yaxis2=dict(domain=[0.5, 1], anchor="x2") # 第二个子图在右侧上方 ) # 更新每个trace的坐标轴引用 fig_free.update_traces(xaxis="x1", yaxis="y1", selector={"name": "Trace 1"}) fig_free.update_traces(xaxis="x2", yaxis="y2", selector={"name": "Trace 2"}) fig_free.show() ``` ![](https://cdn.res.knowhub.vip/c/2504/19/8b414281.png?G1IAAER17rxg3aTG%2bZ14TBMEEmgGGJIIKtWs17v3XLfI94fBSn56bSPWh99rGyHXAVAMRhSkrg5nobsikSfM1JlnDw%3d%3d) ## 子图共享坐标轴 在多子图的情况下,**共享坐标轴**是一个非常实用的功能,通过设置`shared_xaxes=True`或`shared_yaxes=True`,可以让多个子图在同一个坐标轴上进行联动。 这样,当我们在一个子图上进行缩放或平移操作时,其他共享相同坐标轴的子图也会同步更新,从而方便我们进行多数据的对比分析。 此外,当遇到不同量纲的数据时,我们还可以通过`secondary_y=True`来独立控制次坐标轴,避免因量纲冲突而导致图表显示不清晰。 ```csharp fig = make_subplots( rows=2, cols=1, shared_xaxes=True, specs=[[{"secondary_y": True}], [{}]], ) fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]), row=1, col=1) fig.add_trace(go.Scatter(x=[1, 2, 3], y=[40, 50, 60]), row=1, col=1, secondary_y=True) fig.add_trace(go.Scatter(x=[1, 2, 3], y=[7, 8, 9]), row=2, col=1) fig.show() ``` ![](https://cdn.res.knowhub.vip/c/2504/19/df02a551.gif?G1MAAMTm2vOlnAQJ%2fZ62RW6WbdYMMKQRVKpZr%2fv%2fv3UQ9ScYLPl825ixPvzexgyyDVBisEKQQnG4iroXTsVMUFFtz9Ev) # 实战案例 下面两个案例根据实际情况简化而来,主要演示布局如何在实际项目中使用。 ## 股票多指标分析仪表盘 示例中先构造一些模拟数据,然后采用3行1列的布局模式来显示不同的股票信息。 ```csharp import plotly.graph_objects as go from plotly.subplots import make_subplots import pandas as pd # 模拟股票数据 df = pd.DataFrame( { "date": pd.date_range(start="2023-01-01", periods=100), "price": [100 + i * 0.5 + (i % 10) * 2 for i in range(100)], "volume": [5000 + i * 10 + abs(i % 20 - 10) * 100 for i in range(100)], "rsi": [50 + i % 15 - 7.5 for i in range(100)], } ) # 1. 创建3行1列的子图布局 fig = make_subplots( rows=3, cols=1, shared_xaxes=True, # 共享x轴 vertical_spacing=0.05, # 子图间距 subplot_titles=("价格趋势", "成交量", "RSI 指标"), ) # 2. 添加价格走势图 fig.add_trace( go.Candlestick( x=df["date"], open=df["price"] * 0.99, high=df["price"] * 1.02, low=df["price"] * 0.98, close=df["price"], name="股票价格", ), row=1, col=1, ) # 3. 添加成交量柱状图 fig.add_trace( go.Bar(x=df["date"], y=df["volume"], name="成交量", marker_color="lightgray"), row=2, col=1, ) # 4. 添加RSI指标图 fig.add_trace( go.Scatter(x=df["date"], y=df["rsi"], name="RSI", line=dict(color="blue")), row=3, col=1, ) # 5. 更新布局设置 fig.update_layout( title_text="多指标仪表盘", height=800, margin=dict(l=20, r=20, t=80, b=20), # 主图坐标轴配置 xaxis=dict(domain=[0, 1], rangeslider_visible=False), # 成交量图坐标轴 xaxis2=dict(domain=[0, 1], matches="x"), # RSI图坐标轴 xaxis3=dict(domain=[0, 1], matches="x"), # 公共y轴配置 yaxis=dict(domain=[0.7, 1], showticklabels=False), yaxis2=dict(domain=[0.35, 0.65], showticklabels=False), yaxis3=dict(domain=[0, 0.3], tickformat=".0%"), ) # 6. 添加形状标注 fig.add_shape( type="line", x0="2023-01-01", y0=30, x1="2023-04-10", y1=70, line=dict(color="red", width=2, dash="dash"), ) fig.show() ``` ![](https://cdn.res.knowhub.vip/c/2504/19/0592204a.png?G1MAAMTsdJzIi4Sm26hD2jvFHc0AQxpBpZr1es9Z%2byb6fmewxGe0Pn1%2f%2bL316XRlQInBCkEIyWAqaoYSklThXIvWuIYD) 这是单列的布局,如果指标多的话,可以用多列的**网格布局**方式来布局。 ## 物联网设备状态监控 这个示例采用**自由布局**实现主监控图+4个状态指标环绕。 ```csharp import plotly.graph_objects as go from plotly.subplots import make_subplots import numpy as np # 模拟设备数据 np.random.seed(42) device_data = { "timestamp": np.arange(100), "temp": 25 + np.random.normal(0, 2, 100), "pressure": 100 + np.random.normal(0, 5, 100), "vibration": np.random.exponential(0.5, 100), } # 1. 创建2行2列的子图布局 fig = make_subplots( rows=2, cols=2, subplot_titles=("温度", "压力", "振动", "状态"), specs=[ [{"type": "scatter"}, {"type": "scatter"}], [{"type": "scatter"}, {"type": "indicator"}], ], ) # 2. 添加温度折线图 fig.add_trace( go.Scatter( x=device_data["timestamp"], y=device_data["temp"], mode="lines+markers", name="温度 (°C)", line=dict(color="firebrick"), ), row=1, col=1, ) # 3. 添加压力散点图 fig.add_trace( go.Scatter( x=device_data["timestamp"], y=device_data["pressure"], mode="markers", name="压力 (kPa)", marker=dict(size=8, color="royalblue", opacity=0.7), ), row=1, col=2, ) # 4. 添加振动频谱图 fig.add_trace( go.Scatter( x=device_data["timestamp"], y=device_data["vibration"], mode="lines", name="振动 (g)", line=dict(color="forestgreen"), ), row=2, col=1, ) # 5. 添加状态指示器 fig.add_trace( go.Indicator( mode="gauge+number", value=85, domain={"x": [0, 1], "y": [0, 1]}, title="系统健康 (%)", gauge={ "axis": {"range": [0, 100]}, "bar": {"color": "gold"}, "steps": [ {"range": [0, 70], "color": "red"}, {"range": [70, 90], "color": "orange"}, {"range": [90, 100], "color": "green"}, ], }, ), row=2, col=2, ) # 6. 更新布局设置 fig.update_layout( title_text="IoT 设备监控仪表盘", height=600, margin=dict(l=20, r=20, t=80, b=20), showlegend=False, # 温度图坐标轴 xaxis=dict(domain=[0, 0.45], showgrid=False), yaxis=dict(domain=[0.55, 1], showgrid=False), # 压力图坐标轴 xaxis2=dict(domain=[0.55, 1], showgrid=False), yaxis2=dict(domain=[0.55, 1], showgrid=False), # 振动图坐标轴 xaxis3=dict(domain=[0, 0.45], showgrid=False), yaxis3=dict(domain=[0, 0.45], showgrid=False), ) fig.show() ``` ![](https://cdn.res.knowhub.vip/c/2504/19/1a76ab46.png?G1MAAER17rxgZTClfice0wSBBJsBhjSCSjXr9fz%2f2pfI%2b7lCS7xH69P3h99bny41AyYKNRSEkAhaMdbDQuLJTCpSXMMB) # 总结 在`Plotly`中,**子图布局**对于创建高质量的数据可视化作品至关重要。 在实际应用中,对于复杂的仪表盘项目,优先采用**网格布局**可以保证子图之间的对齐和一致性;而对于一些创意性的场景,**自由布局**则能够更好地发挥我们的想象力。