Figure Friday 2024 - week 45

Hello @natatsypora, great job on this visualization, a very innovative to way to implement a Gantt chart. Wondering how it would work when the time/date units are Date objects and not integer number of years. It would be nice if px.timeline or it’s graph object equivalent could offer additional styling for Gantt charts similar to what you have done using a scatterplot.

2 Likes

Hello @Mike_Purtell ,

Thank you for the review. :pray: :blush:
The Scatter Plot works great with months and days, I used an example from Gantt charts in Python to test it :woman_technologist:

View code
df = pd.DataFrame([
    dict(Task="Job A", Start='2024-01-01', Finish='2024-02-28', Resource="Alex", Completion_pct=100),
    dict(Task="Job B", Start='2024-03-05', Finish='2024-04-15', Resource="Alex", Completion_pct=50),
    dict(Task="Job C", Start='2024-02-20', Finish='2024-05-30', Resource="Max", Completion_pct=30),
    dict(Task="Job D", Start='2024-01-05', Finish='2024-05-25', Resource="Bob", Completion_pct=70)
])
fig = go.Figure()
for row in df.sort_values(by='Finish').itertuples():

    # Add traces for each task in data:
    fig.add_trace(go.Scatter(x=[row.Start, row.Finish], marker_color='forestgreen', 
                             line_width=3, marker_size=10, customdata=[row.Task, row.Task],
                             hovertemplate='%{customdata} <br>Resource: %{y}<br>%{x}<extra></extra>',
                             y=[row.Resource, row.Resource], orientation='h', name=row.Task ))
    
# Update color for markers by condition   
for trace in fig.data:
    if trace['name'] == 'Job A':
        trace['marker']['color'] = 'darkgrey' 

# Update layout
fig.update_layout(title='Timeline using go.Scatter', title_x=0.5, title_font_size=20,
                  showlegend=False, height=300, template='plotly_white', width=800,
                  xaxis_ticklabelstandoff=10) 
  
fig.show(config = {'displayModeBar': False})   

Never worked with Gantt charts before, but there are enough interesting examples from the link above. :+1:

Click to view more

fig = px.timeline(df, x_start="Start", x_end="Finish", y="Resource", 
                  hover_data='Task', color='Completion_pct',
                  color_continuous_midpoint=df['Completion_pct'].mean(), # the color scale centered around the mean value
                  color_continuous_scale='greens')
fig.update_traces(width=0.5)
fig.update_layout(title='Timeline with colorscale', title_x=0.5, title_font_size=20,
                  showlegend=False, height=300,  width=800, 
                  template='plotly_white', yaxis_title=None,
                  coloraxis_colorbar=dict(orientation='h',
                                          len=0.8,
                                          thickness=20, #change the width of the color bar.
                                          yanchor='bottom', y=-0.8, xanchor='center', x=0.5 ))  
fig.show()

Dash has a great example of a chart with editable table! :star_struck:

In addition, you can add a style for bars as in bullet charts :upside_down_face:

Code
from datetime import datetime

# Get today's date
today = datetime.today()

data = pd.DataFrame([
    dict(Task="Job A", Start='2024-01-01', Finish='2024-12-31', Resource="Alex", Completion_pct=90),
    dict(Task="Job B", Start='2024-03-05', Finish='2025-01-15', Resource="Tom", Completion_pct=60),
    dict(Task="Job C", Start='2024-02-20', Finish='2025-03-31', Resource="Max", Completion_pct=55),
    dict(Task="Job D", Start='2024-02-05', Finish='2025-02-25', Resource="Bob", Completion_pct=70)
])
#----------------------------------------------------------------------------------------------------

def calculate_progress_date(df, start_col, finish_col, progress_pct_col):
    """
    Calculate the progress date for each task based on the percentage of progress.

    Parameters:
    - df: pd.DataFrame, the input DataFrame containing the data.
    - start_col: str, the name of the column with the start date.
    - finish_col: str, the name of the column with the finish date.
    - progress_pct_col: str, the name of the column with the progress percentage.

    Returns:
    - pd.DataFrame, the DataFrame with an additional column for progress date.
    """
    # Convert columns to datetime if they are not already
    df[start_col] = pd.to_datetime(df[start_col])
    df[finish_col] = pd.to_datetime(df[finish_col])
    
    # Calculate the duration
    df['Duration'] = df[finish_col] - df[start_col]
    df['Duration_days'] = df['Duration'].dt.days # -> return int

    # Calculate the progress duration based on the percentage of progress
    df['Progress_Duration'] = df['Duration'] * df[progress_pct_col] / 100

    # Calculate the progress date by adding the progress duration to the start date
    df['Progress_Date'] = df[start_col] + df['Progress_Duration']

    return df

# Calculate progress dates
df_test = calculate_progress_date(data, 'Start', 'Finish', 'Completion_pct').sort_values(by='Completion_pct')

# Determine the color based on the Progress_Date condition
color = ['green' if row.Progress_Date >= today else 'navy' for row in df_test.itertuples()]

# Create timeline with progress bar==============================
# Add base timeline 
fig_base = px.timeline(df_test, x_start="Start", x_end="Finish", y="Resource", 
                       hover_data=['Task', 'Duration_days', 'Completion_pct'] )
# Add progress bar
fig_progress = px.timeline(df_test, x_start="Start", x_end="Progress_Date", y="Resource", 
                           hover_data=['Completion_pct'])
fig_progress.update_traces(width=0.3, marker_color=color, 
                           hovertemplate='Completion %{customdata}%')
# Add  bars for today
fig_today = px.timeline(df_test, x_start="Start", x_end=[today]*len(df_test), y="Resource")
fig_today.update_traces(marker_color='lightgrey', hovertemplate='Today %{x}')

# Add traces to base timeline
fig_base.add_traces([fig_today.data[0], fig_progress.data[0]])

fig_base.update_layout(title='Timeline with progress bar', title_x=0.4, title_font_size=20,
                       showlegend=False, height=300,  width=800, 
                       template='plotly_white', yaxis_title=None,
                       xaxis_ticklabelstandoff=10)

fig_base.show()