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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 18:47:30