Combining Table with Gantt Chart as subplot

What I am trying to do

Hello, I’m trying to make something that looks similar to this excel spreadsheet. It has a Gantt chart on the right side and a table that it uses as its “y axis” on the left side

What I already have

I’ve only achieved the Gantt chart part, that is currently looking like this:

My chart

Code
import plotly.figure_factory as ff
from datetime import datetime

df = [
    dict(Task="Job A", Start=datetime(2009, 1, 1), Finish=datetime(2009, 2, 28), Resource="Banana"),
    dict(Task="Job B", Start=datetime(2009, 3, 5), Finish=datetime(2009, 4, 15), Resource="Apple"),
    dict(Task="Job C", Start=datetime(2009, 2, 20), Finish=datetime(2009, 5, 30), Resource="Grape"),
]

fig = ff.create_gantt(df, colors="#4FADEA", title='')

annotations = []

for i, x in enumerate(df):
    diff = abs(x['Finish'] - x['Start']) / 2
    middle = x['Start'] + diff

    title = dict(
            x=middle,
            y=i + 0.3,
            text=x['Resource'],
            showarrow=False,
            font=dict(color="black"),
        )

    start_date = title.copy()
    start_date.update(x=x['Start'], text=x['Start'].strftime('%d.%m'))

    end_date = title.copy()
    end_date.update(x=x['Finish'], text=x['Finish'].strftime('%d.%m'))

    annotations += [title, start_date, end_date]

fig["layout"]["annotations"] = annotations
fig['layout']['xaxis']['rangeselector']['visible'] = False
fig.update_xaxes(tickformat='%b-%y\nКвартал %q')
fig.update_yaxes(tickmode='array', tickvals=[])

fig.update_layout(margin = {'l':0,'r':0,'t':0,'b':0},)

# fig.write_image("fig1.png")
fig.show(config=dict(displayModeBar = False))

And I have also managed to create a table

Table

Code
import random

import plotly.graph_objects as go

fig = go.Figure(
    data=[
        go.Table(
            header=dict(values=["ID", "Name", 'Property 1', 'Property 2', 'Property 4', 'Property 5']),
            cells=dict(values=[
                    [1, 2, 3, 4, 5, 6],
                    ['JOB A', 'JOB B', 'JOB C', 'JOB D', 'JOB E', 'JOB F'],
                    ['m3', 'm3', 'm3', 'm3', 'm3', 'm3'],
                    [random.randint(100, 1000) for i in range(6)],
                    ['0 days', '10 days', '20 days', '30 days', '40 days', '50 days'],
                    ['43242/32423', '43242/32423', '43242/32423', '43242/32423', '43242/32423', '43242/32423', ],
                ]
            ),
        )
    ]
)
fig.show()

What have I tried

I’ve tried making subplots with from plotly.subplots import make_subplots like this:

Code
from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(rows=1, cols=2)

fig.add_trace(
    go.Table(
        header=dict(values=["A Scores", "B Scores"]),
        cells=dict(values=[[100, 90, 80, 90], [95, 85, 75, 95]]),
    ),
    row=1, col=1
)

fig.add_trace(
    go.Table(
        header=dict(values=["ID", "Name", 'Property 1', 'Property 2', 'Property 3', 'Property 4', 'Property 5']),
        cells=dict(values=[
            [100, 90, 80, 90],
            [95, 85, 75, 95],
        ]),
    ),
    row=1, col=2
)

fig.update_layout(height=600, width=800, title_text="Side By Side Subplots")
fig.show()
Error

ValueError: Trace type 'table' is not compatible with subplot type 'xy'


And I also tried making my Gantt chart into a subplot

Code
import plotly.figure_factory as ff
from datetime import datetime

from plotly.subplots import make_subplots

df = [
    dict(
        Task="Job A",
        Start=datetime(2009, 1, 1),
        Finish=datetime(2009, 2, 28),
        Resource="Banana",
    ),
    dict(
        Task="Job B",
        Start=datetime(2009, 3, 5),
        Finish=datetime(2009, 4, 15),
        Resource="Apple",
    ),
    dict(
        Task="Job C",
        Start=datetime(2009, 2, 20),
        Finish=datetime(2009, 5, 30),
        Resource="Grape",
    ),
]

fig = ff.create_gantt(df, colors="#4FADEA", title="")

annotations = []

for i, x in enumerate(df):
    diff = abs(x["Finish"] - x["Start"]) / 2
    middle = x["Start"] + diff

    title = dict(
        x=middle,
        y=i + 0.3,
        text=x["Resource"],
        showarrow=False,
        font=dict(color="black"),
    )

    start_date = title.copy()
    start_date.update(x=x["Start"], text=x["Start"].strftime("%d.%m"))

    end_date = title.copy()
    end_date.update(x=x["Finish"], text=x["Finish"].strftime("%d.%m"))

    annotations += [title, start_date, end_date]

fig["layout"]["annotations"] = annotations
fig["layout"]["xaxis"]["rangeselector"]["visible"] = False
fig.update_xaxes(tickformat="%b-%y\nКвартал %q")
fig.update_yaxes(tickmode="array", tickvals=[])

fig.update_layout(
    margin={"l": 0, "r": 0, "t": 0, "b": 0},
)

# fig.write_image("fig1.png")
# fig.show(config=dict(displayModeBar=False))
import plotly.graph_objects as go

layout = go.Layout(
    xaxis=dict(
        domain=[0, 0.45]
    ),
    yaxis=dict(
        domain=[0, 0.45]
    ),
    xaxis2=dict(
        domain=[0.55, 1]
    ),
    xaxis4=dict(
        domain=[0.55, 1],
        anchor="y4"
    ),
    yaxis3=dict(
        domain=[0.55, 1]
    ),
    yaxis4=dict(
        domain=[0.55, 1],
        anchor="x4"
    )
)

plot = make_subplots(rows=1, cols=1)
plot.add_trace(
    go.Figure(fig, layout=layout),
    row=1, col=1
)
plot.show()
Error
    raise ValueError(
ValueError: 
    Invalid element(s) received for the 'data' property of 
        Invalid elements include: [Figure({
    'data': [{'fill': 'toself',
...

Question

Is there any chance someone will help me to combine a table and a Gantt chart into one figure please?

Hi @Neykuratick !
Welcome on the forum! :tada:

First thing, make_subplots is made to use go figures, so you can provide a go.Table() but you must provide the type of the figure, the default is ‘xy’ meaning 2D Cartesian subplot, suitable for most used figures.
That’s why you get the error:

Take a look at:

Then concerning the gantt chart, it is no possible to provide directly as there is no go figure to build it.
You used the figure_factory but it is deprecated, it is better to use px.timeline()
And using either will actually create a go.Bar() figure. So you should be able to provide it to make_subplots.

But to do it, you must take into account that make_subplots will be in charge of creating the domains of the subplots.

So here are the steps to follow:

  • Create your gantt chart figure
  • Remove the x an y domains of the gantt chart figure
  • Provide only the traces to make_subplots
  • Update the layout of the make_subplots figure with the layout (without the domains) of the gantt chart, to apply the remaining layout properties useful to your gantt char

And here is an example:

Code
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots

df = pd.DataFrame([
    dict(Task="Job A", Start='2009-01-01', Finish='2009-02-28', Resource="Alex"),
    dict(Task="Job B", Start='2009-03-05', Finish='2009-04-15', Resource="Alex"),
    dict(Task="Job B", Start='2009-01-01', Finish='2009-02-18', Resource="Max"),
    dict(Task="Job C", Start='2009-02-20', Finish='2009-05-30', Resource="Max")
])

fig = make_subplots(
    rows=1, cols=2,
    specs=[[{"type": "table"}, {"type": "bar"}]]
)

fig.add_trace(
    go.Table(
        header_values=df.columns,
        cells_values=[df[k].tolist() for k in df.columns],
    ),
    row=1, col=1
)

# Create the gantt chart
fig_gantt = px.timeline(df, x_start="Start", x_end="Finish", y="Task", color="Resource")
fig_gantt.update_yaxes(autorange="reversed")  # otherwise tasks are listed from the bottom up

# remove the domains, as make_subplots will deal with that
fig_gantt.layout.xaxis.domain = None
fig_gantt.layout.yaxis.domain = None

# add the traces to make_subplots
for trace in fig_gantt.data:
    fig.add_trace(trace, row=1, col=2)

# apply the remaining layout properties of the gantt chart
fig.update_layout(fig_gantt.layout)

fig.show()

Et voilà!
Probably still need to apply some styling, but you get what you need :confetti_ball:

1 Like

You can find another example here:

2 Likes

Thank you so much! It really helped me. Don’t you know, by the way, how to make table column’s width the same as the text width inside it? I found columnwidth property, but it is in relative units and doesn’t allow to transform columns depending on the text inside them

To be honest I didn’t know go.Table() existed before you mentioned it :laughing:
Indeed it seems you cannot set the width to fit the content…

I would suggest to use Dash like the really nice app linked by @AnnMarieW, even better use the quite new Dash AG Grid component for your table:

You will unlock so much possibilities with Dash + Dash AG Grid :ok_hand:

To fit the column width with the content, it is as easy as to set columnSize="autoSize" :grin:

1 Like