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

基于Plotly实现分组甘特图:图例与颜色按Phase区分

用Plotly实现分组甘特图(按项目分组,按阶段着色)

需求描述

需要用Plotly生成甘特图,数据结构如下(subproject为唯一编码):

project     start       end      phase       decision    subproject
    1      02-2017    03-2018   Phase_1       09-2023        a1
    1      08-2017    07-2019   Phase_1,2     09-2023        a2
    1      02-2018    11-2021   Phase_2       09-2023        a3
    1      04-2021    02-2023   Phase_3       06-2022        a4
    2      01-2019    02-2022   Phase_1       06-2022        b1
    2      06-2019    07-2022   Phase_2       06-2022        b2
    2      01-2021    03-2023   Phase_2,3     06-2022        b3
    2      03-2022    02-2021   Phase_3       06-2022        b4
    3      11-2017    02-2019   Phase_1       06-2022        c1
    3      01-2018    06-2019   Phase_2       06-2022        c2
    3      02-2018    07-2020   Phase_2       06-2022        c3
    3      02-2019    06-2021   Phase_2,3     03-2023        c4
    4      10-2019    10-2019   Phase_2       03-2023        d1
    4      06-2019    08-2020   Phase_3       03-2023        d2
    4      02-2020    02-2021   Phase_3       03-2023        d3

核心目标

  • 每个项目下的子项目条形图分组展示,互不重叠
  • 图例和条形颜色依据phase列设置(多阶段如Phase_1,2使用渐变颜色)
  • 尝试遍历设置颜色但未成功,现有代码如下:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import plotly.express as px

n_colors = 20  # Number of colors in each gradient
blue_orange_cmap = mcolors.LinearSegmentedColormap.from_list("blue_orange", ["blue", "orange"])
orange_green_cmap = mcolors.LinearSegmentedColormap.from_list("orange_green", ["orange", "green"])

blue_orange_colors = [blue_orange_cmap(i/n_colors) for i in range(n_colors)]
orange_green_colors = [orange_green_cmap(i/n_colors) for i in range(n_colors)]

colors = {'Phase_1' : 'blue',
          'Phase_2' : 'orange',
          'Phase_3' : 'green',
          'Phase_1,2' : blue_orange_colors,
          'Phase_2,3' : orange_green_colors}

for dat in fig.data:
    try:
        ix = example_df.index[example_df['subproject']==dat.name]
        phase = example_df.loc[ix, 'phase'].values[0]
        color = colors[phase]
        dat.marker.color = color
        dat.name = phase
    except:
        pass

fig = px.timeline(example_df, x_start='start', x_end='end', y='project', color = 'subproject')

fig.update_layout(xaxis_title='timeline', yaxis_title='projects', showlegend=True, barmode='group', title = "Example", width=1000, height=500)

fig.update_layout(legend=dict(title_text='', traceorder='reversed', itemsizing='constant'))

fig.show()

解决方案

关键问题分析

  1. 原代码中fig在遍历操作后才创建,导致遍历修改无效
  2. Plotly的timeline默认将同y值的条形重叠,需通过y轴偏移实现分组
  3. Matplotlib颜色对象需转换为Plotly支持的Hex格式

完整实现代码

import pandas as pd
import matplotlib.colors as mcolors
import plotly.express as px

# 构建数据集
example_df = pd.DataFrame({
    'project': [1,1,1,1,2,2,2,2,3,3,3,3,4,4,4],
    'start': ['02-2017','08-2017','02-2018','04-2021','01-2019','06-2019','01-2021','03-2022','11-2017','01-2018','02-2018','02-2019','10-2019','06-2019','02-2020'],
    'end': ['03-2018','07-2019','11-2021','02-2023','02-2022','07-2022','03-2023','02-2021','02-2019','06-2019','07-2020','06-2021','10-2019','08-2020','02-2021'],
    'phase': ['Phase_1','Phase_1,2','Phase_2','Phase_3','Phase_1','Phase_2','Phase_2,3','Phase_3','Phase_1','Phase_2','Phase_2','Phase_2,3','Phase_2','Phase_3','Phase_3'],
    'decision': ['09-2023','09-2023','09-2023','06-2022','06-2022','06-2022','06-2022','06-2022','06-2022','06-2022','06-2022','03-2023','03-2023','03-2023','03-2023'],
    'subproject': ['a1','a2','a3','a4','b1','b2','b3','b4','c1','c2','c3','c4','d1','d2','d3']
})

# 1. 数据预处理:转换日期格式,修复错误日期
example_df['start'] = pd.to_datetime(example_df['start'], format='%m-%Y')
example_df['end'] = pd.to_datetime(example_df['end'], format='%m-%Y')
# 修复b4子项目的日期倒序问题
example_df.loc[example_df['subproject'] == 'b4', ['start', 'end']] = example_df.loc[example_df['subproject'] == 'b4', ['end', 'start']].values

# 2. 设置y轴偏移,实现同项目下子项目分组
example_df['y_offset'] = example_df.groupby('project').cumcount() / 10
example_df['y_group'] = example_df['project'] + example_df['y_offset']

# 3. 定义阶段颜色映射,转换为Plotly支持的Hex格式
n_colors = 20
blue_orange_cmap = mcolors.LinearSegmentedColormap.from_list("blue_orange", ["blue", "orange"])
orange_green_cmap = mcolors.LinearSegmentedColormap.from_list("orange_green", ["orange", "green"])

blue_orange_colors = [mcolors.to_hex(blue_orange_cmap(i/n_colors)) for i in range(n_colors)]
orange_green_colors = [mcolors.to_hex(orange_green_cmap(i/n_colors)) for i in range(n_colors)]

colors = {
    'Phase_1': 'blue',
    'Phase_2': 'orange',
    'Phase_3': 'green',
    'Phase_1,2': blue_orange_colors,
    'Phase_2,3': orange_green_colors
}

# 4. 创建甘特图并调整样式
fig = px.timeline(example_df, x_start='start', x_end='end', y='y_group', color='subproject')

# 遍历每个条形,设置对应阶段的颜色
phase_map = example_df.set_index('subproject')['phase'].to_dict()
for trace in fig.data:
    subproject = trace.name
    phase = phase_map[subproject]
    color = colors[phase]
    
    # 多阶段使用渐变中的对应位置颜色
    if isinstance(color, list):
        idx = int(example_df[example_df['subproject'] == subproject]['y_offset'].values[0] * 10)
        trace.marker.color = color[idx]
    else:
        trace.marker.color = color
    
    # 修改trace名称为阶段,统一图例显示
    trace.name = phase

# 调整布局和轴标签
fig.update_layout(
    xaxis_title='时间线',
    yaxis_title='项目',
    title='项目甘特图',
    width=1000,
    height=600,
    legend=dict(title_text='阶段', traceorder='normal')
)

# 调整y轴刻度,显示原项目编号
y_ticks = example_df.groupby('project')['y_group'].mean().reset_index()
fig.update_yaxes(
    tickmode='array',
    tickvals=y_ticks['y_group'],
    ticktext=y_ticks['project']
)

fig.show()

核心实现要点

  • 日期处理:必须将字符串日期转换为datetime类型,确保Plotly正确渲染时间轴
  • y轴偏移:通过给同项目下的子项目添加小数偏移(如1.1、1.2),实现条形分组不重叠
  • 颜色转换:将Matplotlib生成的渐变颜色转换为Hex格式,适配Plotly的颜色要求
  • 图例优化:将每个条形的名称改为对应阶段,让同阶段的条形在图例中合并显示

内容的提问来源于stack exchange,提问作者Newbielp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 13:27:02