基于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()
解决方案
关键问题分析
- 原代码中
fig在遍历操作后才创建,导致遍历修改无效 - Plotly的
timeline默认将同y值的条形重叠,需通过y轴偏移实现分组 - 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
相关产品推荐
相关产品推荐

