Gerard L
Gerard L

Reputation: 25

Plotly timeline plot multiple tasks per resource

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.

example plot

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

Answers (2)

Derek O
Derek O

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()

enter image description here

Upvotes: 1

r-beginners
r-beginners

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()  

enter image description here

Upvotes: 0

Related Questions