Reputation: 25
I have a dataset in the form of a dataframe structured as follows
Job | Task | Machine | Start | End | color |
---|---|---|---|---|---|
A | Do X | 1 | 0 | 5 | blue |
A | Do Y | 2 | 6 | 14 | blue |
B | Do Q | 3 | 0 | 8 | green |
B | Do Z | 3 | 9 | 12 | green |
B | Do X | 1 | 14 | 17 | green |
C | Do Y | 1 | 6 | 9 | red |
I want to plot these on an interactive timeline using Plotly (e.g. px.timeline), similar to included image below, also known as Gantt-chart. Per machine (y-axis), I want to a colored bar representing a task assigned to that machine, similar to its duration. For my example data, it means that for machine 1, there are three bars colored (0-5, 6-9, 14-17). The tasks should be colored according to a defined job color, as included in the color column.
Ideally, in hovering over the bar, it shows Job - Task, Start - end
.
I use the following;
Plotly 5.18.0
Python 3.9
Pandas 2.1.2
Is there a way to do this?
I played around with Plotly, but only managed to get the jobs on the y-axis (https://plotly.com/python/gantt/). I used https://plotly.com/python/figure-factories/ since the px.timeline does not appear to accept integer values for start and end times.
Upvotes: 2
Views: 463
Reputation: 19610
A horizontal bar graph should work, where you analyze each portion of your df
belonging to a particular machine using df.groupby('Machine')
, and then plot a go.Bar
trace corresponding to each row within that group (using all the information in the row for the color, placement, and duration of each bar).
import numpy as np
import pandas as pd
import plotly.graph_objects as go
df = pd.DataFrame({
'Job': ['A','A','B','B','B','C'],
'Task': ['Do X','Do Y','Do Q','Do Z','Do X','Do Y'],
'Machine': ['1','2','3','3','1','1'],
'Start':[0,6,0,9,14,6],
'End':[5,14,8,12,17,9],
'color':['blue','blue','green','green','green','red']
})
df['Duration'] = df['End'] - df['Start']
fig = go.Figure()
for group_name, df_group in df.groupby('Machine'):
for row_index, row in df_group.iterrows():
start, duration, task, color = row['Start'], row['Duration'], row['Task'], row['color']
# hovertemplate: Job - Task, Start - end
customdata = [[row['Job'], row['Task'], row['Start'], row['End']]] # Custom data for each trace
fig.add_trace(
go.Bar(
x=[duration],
base=[start],
marker=dict(color=color),
y=[f"M{group_name}"],
orientation='h',
showlegend=False,
customdata=customdata,
hovertemplate="%{customdata[0]} - %{customdata[1]}, %{customdata[2]} - %{customdata[3]}<extra></extra>"
)
)
fig.add_annotation(
x=start+(duration/2),
y=f"M{group_name}",
text=task,
font=dict(color='white'),
showarrow=False,
)
fig.update_layout(
xaxis_title='time', yaxis_title='machine',
barmode='stack', bargroupgap=0.0, bargap=0.0
)
fig.show()
Upvotes: 1
Reputation: 35230
In px.timeline
, colors cannot be specified, so we can simply use a horizontal bar graph.
To draw a horizontal bar chart with a specified color, specify a stacked bar chart in a loop process with the contents conditionally extracted from the data frame by Job. The base is specified because it is always necessary to set the starting position. Text display can only be selected inside and outside, so we add it to the center of the bar chart using string annotations
import plotly.graph_objects as go
fig = go.Figure()
for j in df['Job'].unique():
dfj = df.query('Job == @j')
for row in dfj.itertuples():
print(row)
before_base = row.Start
fig.add_trace(go.Bar(
base=[row.Start],
x0=[row.Start],
x=[row.End- before_base],
y=[row.Job],
y0=[row.Job],
hovertemplate='Start: '+str(row.Start)+'<br>'+
'End: %{x}<br>'+row.Task+'<extra></extra>',
orientation='h',
#text=row.Task,
marker_color=row.color,
width=0.9,
showlegend=False,
))
fig.add_annotation(
x=(row.Start+row.End)/2,
y=row.Job,
text=row.Task,
font=dict(color='white'),
showarrow=False,
)
fig.update_layout(barmode='stack')
fig.update_yaxes(autorange="reversed")
fig.update_layout(height=400)
fig.show()
Upvotes: 0