7

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()
Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
RoQuOTriX
  • 2,871
  • 14
  • 25
  • Did you try if Plotly works only with dates? I can't test it right now, but I would imagine that it could also work with numerical values as the dates are usually also transformed to numericals for plotting. – Axel Aug 28 '19 at 07:40
  • @Axel yes I tried with Integers and Floats. Plotly seems to convert these values to dates, because 0 equals sth like 1970 – RoQuOTriX Aug 28 '19 at 07:45
  • @RoQuOTriX Ah okay that's a pity. But maybe your best bet is to rephrase your question how to get Plotly to work with numerical values. Because from what I know Plotly is the most advanced interactive plotting tool. There is also [Pygal](http://pygal.org), but I couldn't find anything about Gantt charts on their website. – Axel Aug 28 '19 at 07:52
  • 1
    Your edit improves this question and I makes it specific enough to be on-topic, thanks for taking on board my comments. I've retracted my close vote and updated your tags, hope you find a solution. – Wolfie Aug 28 '19 at 14:25

2 Answers2

4

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.

Axel
  • 1,415
  • 1
  • 16
  • 40
1

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

luomein
  • 51
  • 1
  • 4