Using numerical values in plotly for creating Gantt-Charts

Question:

I want to create interactive Gantt-Charts (or a sequence chart) for displaying the scheduling of tasks on multiple processors.

I found the library plotly, which produced very good and interactive Gantt-charts. Unfortunately, plotly-Gantt only works with dates and not numerical values, like I have with the runtime values of the schedule.

Is there a possibility to create Gantt-charts in plotly with numerical values?

Code Example: (I would like to use something like this)

import plotly.figure_factory as ff

df = [dict(Task="Job A on Core 0", Start=0, Finish=10),
      dict(Task="Job B on Core 1", Start=2, Finish=8),
      dict(Task="Job C on Core 0", Start=11, Finish=12)]

fig = ff.create_gantt(df)
fig.show()
Asked By: RoQuOTriX

||

Answers:

So I tried to get Plotly’s figure_factory function create_gantt to work with numerical values. The only thing that I came up with is a rather dirty work-around that looks like this:

import plotly.figure_factory as ff
from datetime import datetime
import numpy as np

def convert_to_datetime(x):
  return datetime.fromtimestamp(31536000+x*24*3600).strftime("%Y-%d-%m")

df = [dict(Task="Job A", Start=convert_to_datetime(0), Finish=convert_to_datetime(4)),
      dict(Task="Job B", Start=convert_to_datetime(3), Finish=convert_to_datetime(6)),
      dict(Task="Job C", Start=convert_to_datetime(6), Finish=convert_to_datetime(10))]

num_tick_labels = np.linspace(start = 0, stop = 10, num = 11, dtype = int)
date_ticks = [convert_to_datetime(x) for x in num_tick_labels]

fig = ff.create_gantt(df)
fig.layout.xaxis.update({
        'tickvals' : date_ticks,
        'ticktext' : num_tick_labels
        })
fig.write_html('first_figure.html', auto_open=True)

The function convert_to_datetime takes an integer and converts it to datetime string starting at 1971-01-01 for x=0 and increases by one day per increment of x. This function is used to convert all numerical values that you might want to use in your Gantt chart into date strings. Here I just inserted integers from 0 to 10 to showcase that this actually works.

Then for the tick labels the lowest (0) and largest (10) values are used to create evenly distributed tick labels. These integers are then also converted to date strings using list comprehension.

Finally if you execute the whole thing you will get an interactive Gantt chart that looks like this:

Interactive Gantt chart using Plotly

I believe this approach can definitely be improved to get a better workflow, but you could use it as a starting point.

Answered By: Axel

Based on Axel’s answer, instead of plotly.figure_factory.create_gantt, use plotly.express.timeline to add hovertemplate showing customdata. And embed into subplot.

import plotly.figure_factory as ff
import plotly.express as px
from plotly.subplots import make_subplots
import numpy as np
from datetime import datetime as dt, timedelta
import pandas as pd

def convert_to_datetime(x, strftime_format = "%Y-%m-%d"):
    return dt.fromtimestamp(31536000+x*24*3600).strftime(strftime_format)

def create_gantt(subdf, Task_column, num_Start_column, num_Finish_column, color_list, tick_num = 3  ):
    subdf["Task"] = subdf[Task_column]
    subdf["Start"] = subdf[num_Start_column].apply(convert_to_datetime) 
    subdf["Finish"] = subdf[num_Finish_column].apply(convert_to_datetime)
     
    num_tick_labels = np.linspace(start = min(np.min(subdf[num_Start_column]),np.min(subdf[num_Finish_column])),
                                  stop = max(np.max(subdf[num_Start_column]),np.max(subdf[num_Finish_column])), num = tick_num, dtype = int)
    date_ticks = [convert_to_datetime(x) for x in num_tick_labels]
    px_fig = px.timeline(subdf,x_start="Start", x_end="Finish", y="Task", color_discrete_sequence=color_list , hover_data=[num_Start_column, num_Finish_column])
    for trace in range(len(px_fig["data"])):
        px_fig["data"][trace]["hovertemplate"] = 'Start=%{customdata[0]}<br>Finish=%{customdata[1]}'
    px_fig.layout.xaxis.update({
        'tickvals' : date_ticks,
        'ticktext' : num_tick_labels
        })
    
    return px_fig 

def embed_in_subplot(parent_fig, child_fig,  row, col ):
    for trace in range(len(child_fig["data"])):
      parent_fig.add_trace(child_fig["data"][trace],
                    row=row, col=col)
    parent_fig.update_xaxes(tickvals=child_fig.layout.xaxis.tickvals, ticktext=child_fig.layout.xaxis.ticktext, type=child_fig.layout.xaxis.type, row=row, col=col)
    parent_fig.update_yaxes(tickvals=child_fig.layout.yaxis.tickvals, ticktext=child_fig.layout.yaxis.ticktext, type=child_fig.layout.yaxis.type, row=row, col=col)

    return parent_fig
def test_gantt():
    df1 = [dict(Task="Job A", numStart=530 , numFinish=4456),
      dict(Task="Job B", numStart=343, numFinish=623),
      dict(Task="Job C", numStart=746, numFinish=1023)]

    df2 = [dict(Task="Job A", numStart=560 , numFinish=756),
      dict(Task="Job B", numStart=143, numFinish=323),
      dict(Task="Job C", numStart=246, numFinish=623)]

    
    df1 = pd.DataFrame(df1)
    df2 = pd.DataFrame(df2)
    child_fig1  = create_gantt(df1, 'Task', 'numStart', 'numFinish',   [px.colors.qualitative.Plotly[0]]* len(df1)  , tick_num = 5  )
    child_fig2  = create_gantt(df2, 'Task', 'numStart', 'numFinish',   [px.colors.qualitative.Plotly[1]]* len(df2)  , tick_num = 5  )
    
    parentfig = make_subplots(rows=1,cols=2)
    parentfig = embed_in_subplot(parentfig, child_fig1,  1,1)
    parentfig = embed_in_subplot(parentfig, child_fig2,  1,2)
    parentfig.show()

#### run test
test_gantt()  

output

Answered By: luomein
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.