How to see all tasks in overlapping timeline hover

I want to be able to see all of the tasks in the hover info for a range of the timeline, but when I have partially overlapping time range, I can only see the bottom task. Is there any way to be able to see everything?

import pandas as pd
import plotly.express as px
from datetime import datetime

resources = ["Resource A", "Resource B"]
tasks = ["Task 1", "Task 2", "Task 3", "Task 4"]

activities = [
    # Resource A activities
    {
        "start": datetime(2026, 2, 25, 8, 0),
        "end": datetime(2026, 2, 25, 11, 0),
        "resource": "Resource A",
        "task": "Task 1",
    },
    {
        "start": datetime(2026, 2, 25, 9, 0),
        "end": datetime(2026, 2, 25, 11, 0),
        "resource": "Resource A",
        "task": "Task 2",
    },
    # Resource B activities
    {
        "start": datetime(2026, 2, 25, 9, 0),
        "end": datetime(2026, 2, 25, 11, 0),
        "resource": "Resource B",
        "task": "Task 3",
    },
    {
        "start": datetime(2026, 2, 25, 10, 0),
        "end": datetime(2026, 2, 25, 11, 0),
        "resource": "Resource B",
        "task": "Task 4",
    },
]

all_windows = pd.DataFrame(activities)

fig = px.timeline(
    all_windows,
    x_start="start",
    x_end="end",
    y="resource",
    color="task",
)

fig.update_layout(
    title_text="Resource Task Timeline",
    yaxis_title="Resources",
    xaxis_title="Time",
)

fig.show()

Hello @loricah welcome to the forums.

I would not know how to do that but IMHO the graph itself is not very useful like that. Have you considered building a custom graph using subplots for each resource and go.Bar() traces? An example:

import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from datetime import datetime
import plotly.express as px  # For the color scales

activities = [
    {"start": datetime(2026, 2, 25, 8, 0), "end": datetime(2026, 2, 25, 11, 0), "resource": "Resource A", "task": "Task 1"},
    {"start": datetime(2026, 2, 25, 9, 0), "end": datetime(2026, 2, 25, 11, 0), "resource": "Resource A", "task": "Task 2"},
    {"start": datetime(2026, 2, 25, 9, 0), "end": datetime(2026, 2, 25, 11, 0), "resource": "Resource B", "task": "Task 3"},
    {"start": datetime(2026, 2, 25, 10, 0), "end": datetime(2026, 2, 25, 11, 0), "resource": "Resource B", "task": "Task 4"},
]

df = pd.DataFrame(activities)
df['duration_ms'] = (df['end'] - df['start']).dt.total_seconds() * 1000

unique_resources = df['resource'].unique()
n_rows = len(unique_resources)

# Mapping resources to specific color scales
# We pick colors from the middle of the scale so they aren't too light or too dark
color_maps = {
    "Resource A": px.colors.sequential.Reds[4:8], 
    "Resource B": px.colors.sequential.Blues[4:8]
}

# subplot for each resource
fig = make_subplots(
    rows=n_rows, 
    cols=1, 
    shared_xaxes=True, 
    vertical_spacing=0.1, 
    subplot_titles=[f"<b>{res}</b>" for res in unique_resources]
)

# 3. Add task traces to corresponding resource
for i, resource in enumerate(unique_resources):
    res_df = df[df['resource'] == resource].reset_index()
    
    # Select the color palette for this resource (default to Greys if not found)
    palette = color_maps.get(resource, px.colors.sequential.Greys[4:8])
    
    # Assign a color to each task by cycling through the chosen palette
    colors = [palette[j % len(palette)] for j in range(len(res_df))]
    
    fig.add_trace(
        go.Bar(
            base=res_df['start'],
            x=res_df['duration_ms'],
            y=res_df['task'],
            orientation='h',
            marker_color=colors, # Apply the list of shades
            name=resource,
            customdata=res_df[['start', 'end']],
            hovertemplate=(
                "<b>%{y}</b><br>" +
                "Start: %{customdata[0]|%H:%M}<br>" +
                "End: %{customdata[1]|%H:%M}<br>" +
                "<extra></extra>"
            ),
            showlegend=False
        ),
        row=i + 1, col=1
    )

# Axis Formatting
fig.update_xaxes(
    type='date',
    tickformat="%H:%M\n%b %d",
    dtick=3600000, # 1 hour in ms
)

fig.update_yaxes(autorange="reversed")

fig.show()


mrep grouping timeline

1 Like

Hi @AIMPED, thank you for taking the time to reply. The graph I provided is a minimal version of a much larger graph. I created it to illustrate the problem, but it is not what I am actually plotting. I do have a version with subplots, but this one in particular must stay as is.

OK, understood.

Maybe you could play around with facet_col facet_row or changing the order and visibility of the traces:

fig.update_traces(opacity=0.6, marker_line_width=1, marker_line_color="black", selector=0, zorder=0)

As I said, I do not think there is much you can do using px.timeline() as is, unfortunately.