Gantt Chart in client side code

Hello Anvil community !

What I’m trying to do:

I currently use an anvil server side function to create a gantt chart using plotly express, then return the figure when calling for this function, and pass it to plot_component.figure in client side.
However, calling the server function every time i want to access the gantt chart can ruin the UX.
I read it’s always possible to use graph_objects to reproduce exactly the express method.
But i just can’t do it.

Here is the server side snippet (some lines may be obsolete or not optimal, i just didn’t clean the code yet):

@anvil.server.callable
def _plot_planning():
    now = datetime.datetime.now(tz=ZoneInfo("UTC")).astimezone(ZoneInfo("Pacific/Tahiti"))
    today = datetime.date.today()
    tomorrow = datetime.date.today() + datetime.timedelta(days=1)
    status_map = {
        'ongoing': 'En cours',
        'booked': 'Réservé',
        'confirmed': 'Confirmé',
        "maintenance": "En maintenance",
        'finished': 'Terminé'
    }
    bookings = app_tables.bookings.search(_business_id=anvil.server.session['business_id'])
    last_day = max([booking['ending'] for booking in bookings])
    df = pd.DataFrame([
            {
                'id': booking['vehicle_immatriculation'],
                'starting': booking['starting'].strftime("%Y-%m-%d %H:%M"),
                'ending': booking['ending'].strftime("%Y-%m-%d %H:%M"),
                'status': status_map[booking['status']],
            }
        for booking in bookings])
    colors = {
        "Réservé": "#FFEE93", 
        "Confirmé": "#68B6EF", 
        "En cours": "#90EE90",
        "En maintenance": "#D1D1D1", 
        "Terminé": "#46484F"
    }
    fig = px.timeline(
        df, x_start="starting", x_end="ending", y="id", color="status", color_discrete_map=colors,
        custom_data="status"
    )
    fig.update_traces(
        #width= 0.4, 
        hovertemplate='<b>%{y}</b> <br><b>Status:</b> %{customdata} <br><b>Départ:</b> %{base|%d/%m %Hh%M} <br><b>Retour:</b> %{x|%d/%m %Hh%M}<extra></extra>' # <b>Status:</b> %{customdata} <br>
    )
    fig.update_layout(
        legend=dict(
            orientation="h",
            yanchor="bottom",
            y=1.05,
            xanchor="right",
            x=1,
        ),
        bargap = 0,
        #plot_bgcolor='white',
        template='seaborn',
        legend_title_text='',
        margin=dict(
            l=0,
            r=10,
            b=10,
            t=10,
            pad=0
        ),
    )
    fig.update_yaxes(
        autorange="reversed",
        automargin=True,
        title_text = "",
        title_standoff = 0,
        showgrid=True,
        showline=True,
    )
    fig.update_xaxes(
        ticks="inside",
        side='top',
        ticklabelstep=1,
        tickformat="%d/%m",
        type='date',
        showgrid=True,
        rangeslider_visible=True,
        showline=True,
        range=[today, last_day]
        #linecolor='lightgrey',
        #gridcolor='lightgrey'
    )
    fig.add_vrect(
        x0=today, 
        x1=tomorrow, 
        fillcolor="#5AC9A1", 
        opacity=0.35, 
        line_width=0
    )
    return fig

I’ve spent a few hours looking for a solution, but only in vain.

Any help will be be largely appreciated !

What do you mean by “graph_objects”? What bad UX affects are you talking about? If you mean the spinner, you can suppress that by passing an argument on the client side when calling the server function:

By graph_objects, i mean the plotly.graph_objects module (i suppose the bar object should do the trick).
And for the UX, i talk about the 5 second loading time since it’s a server function call (the spinner is not the problem, but really the fact that i have to call a server function for this).
I wouldn’t have search for a solution if it was the only solution, but as stated in the anvil docs, plotly.graphic_objects module can be used in the client side code, hence this could potentially be my solution.
This would indeed drastically enhance loading time and UX.

Ok so, this is what i have so far, trying to create bars with starting date as “base” property, and setting a timedelta in milliseconds as the “x” property of each bar.
I did in fact follow this post : https://community.plotly.com/t/horizontal-bar-plotly-with-datetime/66769/7

But no bar is showing.

Here is the code snippet in the client :

def plot_planning(self):
        now = datetime.datetime.now()
        today = datetime.date.today()
        tomorrow = datetime.date.today() + datetime.timedelta(days=1)
        status_map = {
            'ongoing': 'En cours',
            'booked': 'Réservé',
            'confirmed': 'Confirmé',
            "maintenance": "En maintenance",
            'finished': 'Terminé'
        }
        colors = {
            "Réservé": "#FFEE93", 
            "Confirmé": "#68B6EF", 
            "En cours": "#90EE90",
            "En maintenance": "#D1D1D1", 
            "Terminé": "#46484F"
        }
        bookings = [
                {
                    'id': booking['vehicle_immatriculation'],
                    'starting': booking['starting'],
                    'ending': booking['ending'], #.strftime("%Y-%m-%d %H:%M"),
                    'status': status_map[booking['status']],
                }
            for booking in GLOBAL_VARIABLES.global_data['bookings'].values()]
        last_day = max([booking['ending'] for booking in bookings])
        bars = []
        for booking in bookings:
            duration = (booking['ending'] - booking['starting']).total_seconds() * 1000
            bar = go.Bar(
                y=booking['id'],
                x=duration,
                orientation='h',
                base=booking['starting'].strftime("%Y-%m-%d %H:%M"),
                name=booking['status'],
                
            )
            bars.append(bar)
        #fig.update_traces(
        #    #width= 0.4, 
        #    hovertemplate='<b>%{y}</b> <br><b>Status:</b> %{customdata} <br><b>Départ:</b> %{base|%d/%m %Hh%M} <br><b>Retour:</b> %{x|%d/%m %Hh%M}<extra></extra>' # <b>Status:</b> %{customdata} <br>
        #)
        layout = go.Layout(
            legend=dict(
                orientation="h",
                yanchor="bottom",
                y=1.05,
                xanchor="right",
                x=1,
            ),
            bargap = 0,
            #plot_bgcolor='white',
            template='seaborn',
            legend_title_text='',
            margin=dict(
                l=0,
                r=10,
                b=10,
                t=10,
                pad=0
            ),
        )
        yaxis = go.layout.YAxis(
            autorange="reversed",
            automargin=True,
            title_text = "",
            title_standoff = 0,
            showgrid=True,
            showline=True,
        )
        xaxis = go.layout.XAxis(
            ticks="inside",
            side='top',
            ticklabelstep=1,
            tickformat="%d/%m",
            type='date',
            showgrid=True,
            rangeslider_visible=True,
            showline=True,
            range=[today, last_day]
            #linecolor='lightgrey',
            #gridcolor='lightgrey'
        )
        #fig.add_vrect(
         #   x0=today, 
         #   x1=tomorrow, 
         #   fillcolor="#5AC9A1", 
         #   opacity=0.35, 
         #   line_width=0
        #)
        self.plot_1.data = bars
        self.plot_1.layout = layout
        self.plot_1.layout.xaxis = xaxis
        self.plot_1.layout.yaxis = yaxis

Is there any suggestion ?
i can hardly find any help in the plotly docs for gantt charts.
only plotly.express gives this option…

Also… could plotly.express be available to use with plot components anytime soon ?