✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🧬 Learn how to build RNA-Seq data apps with Python & Dash. Register for the May 20 Webinar!

Dealing with overlapping text labels

Hi.

I was attempting to build my own version of a Gantt chart in order to be more flexible with respect to its styling. Borders around the individual boxes do not work well with the officially supported Gantt chart provided via the figure factory.

In order to streamline the process of reading the graph, I want to display the relevant information as text labels on top of the boxes. However, depending on the zoom level, labels will start to overlap with other labels. I went through the plotly documentation and found that, e.g., Funnel and Waterfall charts offer the option to have labels constrained to the shapes of the chart. I have not found a way to transfer utilize this for my own Gantt chart. An alternative would be to not draw overlapping labels at all, but for this I would need to know what the size of the label would be in terms of plot axis coordinates.

Could anyone suggest any solutions to this issue? I have provided example code below.

#!/usr/bin/env python
# coding: utf-8

# In[ ]:


import numpy as np
import pandas as pd
import plotly.graph_objects as go


# In[101]:


# Define tasks

df = pd.DataFrame([
    dict(Task="Job-1", Start='2017-01-01', Finish='2017-02-02', Resource='Complete'),
    dict(Task="Job-1", Start='2017-02-15', Finish='2017-03-15', Resource='Incomplete'),
    dict(Task="Job-2", Start='2017-01-17', Finish='2017-02-17', Resource='Not Started'),
    dict(Task="Job-2", Start='2017-01-17', Finish='2017-02-17', Resource='Complete'),
    dict(Task="Job-3", Start='2017-03-10', Finish='2017-03-20', Resource='Not Started'),
    dict(Task="Job-3", Start='2017-04-01', Finish='2017-04-20', Resource='Not Started'),
    dict(Task="Job-3", Start='2017-05-18', Finish='2017-06-18', Resource='Not Started'),
    dict(Task="Job-4", Start='2017-01-14', Finish='2017-03-14', Resource='Complete'),
])
df["Start"] = pd.to_datetime(df["Start"])
df["Finish"] = pd.to_datetime(df["Finish"])

shifts = []
for i in range(10):
    dfs = df.copy()
    dfs["Start"] += pd.Timedelta("110 days 2 hours") * i
    dfs["Finish"] += pd.Timedelta("110 days 2 hours") * i
    shifts.append(dfs)
df = pd.concat(shifts, axis=0)


# In[102]:


default_colors = [
    '#1f77b4',  # muted blue
    '#ff7f0e',  # safety orange
    '#2ca02c',  # cooked asparagus green
    '#d62728',  # brick red
    '#9467bd',  # muted purple
    '#8c564b',  # chestnut brown
    '#e377c2',  # raspberry yogurt pink
    '#7f7f7f',  # middle gray
    '#bcbd22',  # curry yellow-green
    '#17becf'   # blue-teal
]


# In[103]:


# {n: i for i, n in enumerate(df["Task"].sort_values().unique()))}

# df["Task"].drop_duplicates().sort_values(ascending=False).reset_index(drop=True).to_dict()
# df["rank"] = df["Task"].rank(ascending=False)
# df

order = ["Job-1", "Job-2", "Job-3", "Job-4"]
df["order"] = df["Task"].map({n: i for i, n in enumerate(reversed(order))})

color = {n: default_colors[i] for i, n in enumerate(order)}
df["color"] = df["Task"].map(color)
df


# In[104]:


df["Start"].astype(str)


# In[111]:


def gantt_rectangle(start, finish, y, color, border=False):
    start = str(start.floor("s"))
    finish = str(finish.floor("s"))
    return go.Scatter(
        x=[start, finish, finish, start, start],
        y=[y - 0.4, y - 0.4, y + 0.4, y + 0.4, y - 0.4],
        mode="lines",
        fill="toself",
        fillcolor=color,
        line_color="black",
        hoveron="fills",
        text="Dunno, we'll see.",   
    )


def gantt_label(start, finish, y, text=None):
    return go.Scatter(
        x=[str((start + (finish - start) / 2).floor("s"))],
        y=[y],
        mode="text",
        hoverinfo="skip",
        text="123904589#23<br>Subtext",
    )


def create_gantt(df, order=None, groupby=None):
    df = df.copy()
    
    yticks = df.drop_duplicates(subset=["order"]).sort_values("order")
    
    
    layout = dict(
        #  title="Gantt Chart",
        height=600,
        width=800,
        shapes=[],
        hovermode="closest",
        yaxis=dict(
            ticktext=yticks["Task"].to_list(),
            tickvals=yticks["order"].to_list(),
            range=[-1, len(yticks.index)],
            autorange=False,
            fixedrange=True,
            zeroline=False,
        ),
        xaxis=dict(
            zeroline=False,
            type="date",
        ),
        showlegend=False,
    )
    data = [gantt_rectangle(row["Start"], row["Finish"], row["order"], row["color"]) for i, row in df.iterrows()]
    data += [gantt_label(row["Start"], row["Finish"], row["order"]) for i, row in df.iterrows()]
    
    fig = go.Figure(layout=layout, data=data)
    return fig


# In[112]:


fig = create_gantt(df)
fig.write_html("test.html")