在Plotly中创建具有固定单元格宽度的分组热力图
实现Plotly多分组热力图:等列宽+分组标题兼得
我懂你的需求——要在同一张Plotly图表里展示多组热力图,所有列(不管是同组内还是跨组)宽度必须一致,同时还要给每个分组加上标题。之前的两种思路各有短板:子图方案能加标题但列宽不均,单一热力图能保证列宽却没分组标识。下面结合你的示例数据,给你一个完美兼顾两者的解决方案。
核心思路
我们基于单一热力图(天然保证所有列宽度一致),然后通过Plotly的annotations功能手动添加分组标题,还能加分隔线强化分组视觉效果。这样既保留了等列宽的优势,又实现了分组标题的需求。
完整可运行代码
import numpy as np from numpy.random import default_rng import pandas as pd import plotly.express as px # 生成你提供的示例数据 rng = default_rng(42) n_variates = 2 variables = ['apple','pear', 'orange','mandarin','grapefruit'] sources = ['pome','pome','citrus','citrus','citrus'] variates = np.repeat(list(range(n_variates)),len(variables)) values = rng.random(n_variates * len(variables)) df = pd.DataFrame({'variable':np.tile(variables,n_variates), 'source':np.tile(sources,n_variates), 'variate':variates, 'value':values}) # 整理成宽表,保留层级列名用于后续分组计算 z = df.pivot(columns=['source','variable'],index='variate',values='value') source_labels, variable_labels = z.columns.get_level_values(0), z.columns.get_level_values(1) variate_labels = z.index.tolist() # 1. 创建基础热力图(所有列宽度自动一致) fig = px.imshow(z.values, x=variable_labels, y=variate_labels) fig.update_xaxes(type='category', tickangle=45) # 旋转x轴标签避免重叠 fig.update_yaxes(type='category') # 2. 计算分组标题的位置并添加注释 annotations = [] current_left = -0.5 # Plotly分类轴的起始左边界位置 # 遍历每个分组,计算标题的中心位置 for source, group_df in df.groupby('source'): col_count = group_df['variable'].nunique() group_center = current_left + col_count / 2 # 添加分组标题注释 annotations.append({ 'x': group_center, 'y': 1.1, # 标题放在热力图上方的相对位置 'xref': 'x', 'yref': 'paper', 'text': source, 'showarrow': False, 'font': {'size': 14, 'weight': 'bold'}, 'xanchor': 'center' }) # 更新下一个分组的起始位置 current_left += col_count # 3. 可选:添加分组分隔线,增强视觉区分 current_pos = 0.5 for source, group_df in df.groupby('source'): col_count = group_df['variable'].nunique() current_pos += col_count annotations.append({ 'x': current_pos, 'y': 0, 'xref': 'x', 'yref': 'paper', 'text': '', 'showarrow': False, 'bordercolor': 'black', 'borderwidth': 2, 'height': 100, # 分隔线高度覆盖整个热力图 'yshift': -10 }) # 4. 调整布局,给顶部标题留空间 fig.update_layout( annotations=annotations, margin={'t': 50}, # 顶部边距,避免标题被截断 coloraxis={'colorscale': 'Viridis'} # 可选:自定义颜色刻度 ) fig.show()
代码细节说明
- 基础热力图:用
px.imshow生成的单一热力图,所有列共享同一个分类x轴,因此Plotly会自动给每列分配完全相同的宽度,完美解决列宽不均的问题。 - 分组标题:通过统计每个分组的列数,计算出分组的中心位置,再用
annotations把标题放在对应位置,效果和子图的分组标题完全一致。 - 分隔线:可选的垂直分隔线能让分组边界更清晰,提升图表可读性。
- 布局调整:顶部边距
margin={'t':50}是为了给分组标题留出足够空间,避免被图表顶部截断。
用go.Heatmap的替代版本
如果你习惯用plotly.graph_objects而不是express,可以把基础热力图换成go.Heatmap,其余注释和布局代码完全通用:
import plotly.graph_objects as go # 替换基础热力图部分 fig = go.Figure(data=go.Heatmap( z=z.values, x=variable_labels, y=variate_labels, coloraxis="coloraxis" )) # 后续的annotations和layout代码和上面完全一致 fig.update_xaxes(type='category', tickangle=45) fig.update_yaxes(type='category') fig.update_layout( annotations=annotations, margin={'t': 50}, coloraxis={'colorscale': 'Viridis'} ) fig.show()
这个方案既满足了你对列宽一致性的要求,又实现了分组标题的展示,完全适配你的需求。
内容的提问来源于stack exchange,提问作者Johannes Wiesner
相关产品推荐
相关产品推荐

