dcc.Loading with clientside_callback

Im trying to build a dash app that, ideally, will be used by many clients, with a large database and many possibilities of aggregations. To to that, i created a backend that process the many aggregations to remove some load from the dash server. Still, the load is getting a little too big for the server, so now im switching some callbacks to client side.

When i changed a callback from a regular callback into a clientside, the loading icon stopped working, everything else is working just fine. Am i missing something or the loading component doesn’t work with clientside callbacks?

This is (part of) the code:

layout:

html.Div(
            className="eight columns div-user-controls",
            children=[
                dcc.Loading(
                    id="loading-1",
                    type="default",
                    children=html.Div(
                        id='loading-placeholder',
                        style={},
                        className="plot-area",
                        children=[
                            html.Div(
                                children=[
                                    html.Div(id='plot_cache', children=''),
                                    html.Div(id='load', children='')
                                ],
                                style ={'display':'none'}
                            ),
                            html.Div(
                                children=[
                                    dcc.RadioItems(
                                        id='input_plot_type',
                                        options=[
                                            {'label':'Grouped Bars', 'value':'group'},
                                            {'label':'Stacked Bars', 'value':'stack'},
                                            {'label':'Lines',        'value':'lines'}
                                        ],
                                        value='group',
                                        className='plot-type-switch',
                                        style={},
                                    )
                                ],
                                className='plot-object'
                            ),
                            html.Div(
                                children=[
                                    dcc.Graph(
                                        id='main_plot',
                                        figure=px.bar(pd.DataFrame(columns=['x','y']),x='x',y='y')
                                    )],
                                className='plot-object'
                            )
                        ]
                    )
                )
            ]
)

callback.py:

    dashapp.clientside_callback(
        ClientsideFunction(
            namespace='callback_data',
            function_name='get_data'
        ),
        [
            Output('plot_cache',          'children'),
            Output('loading-placeholder',  'style'),
        ],
        [
            Input('', ''),
            ... # there are a lot of inputs
        ]
    )

and of course there is a .js file, but its just a script that takes ~2s to run and returns some cache and {} as the style.

Thanks!

Hi, I am having the same issue. Were you ever able to find a solution to this? The loading bars are pretty important to let users know the app is working…

Unfortunately, i couldn’t find a direct solution with the dcc.Loading
What i did was a little hack:
First, i created a div on top of the visuals that we were loading:

html.Div( # loading spinner
    id="sidebar-loader-div",
    style={
        'height': '100%',
        'display': 'flex',
        'justify-content': 'center',
        'align-items': 'center',
        'font-size': '0px',
        'position': 'absolute',
        'z-index': '1031',
        'background': 'rgba(125, 125, 125, 0.0)',
    },
    children=[
        html.Div(
            id="sidebar-loader",
            children=[
                html.Img(
                    src="assets/spinner.gif",
                    style={
                        'display': 'block',
                        'width': '80px'
                    }
                )
            ]
        )
    ]
)

(Some cool gifs can bee found here :slight_smile: )
And then i switch the z-index of this div to some low number (-1 in my specific example) to put it behind the actual visual. You can do it using an Output in your callback or access the html element directly in the js file:

document.getElementById("sidebar-loader-div").style['z-index'] = -1;
return [//your outputs];

hope i could have helped you

2 Likes

thank you for replying!

this makes sense to me, and I like this work-around. I am having trouble understanding a few things…

when you say put on top of the visuals, were both components inside the same html.Div (the chart and the loading div)?

is the second section of code what you are saying should go into the clientside callback?

apologies for any dumb questions.

Theres no such thing as a dumb question :slight_smile:

About the html components, what i did was create some sort of “placeholder div” where both the chart and the loader spinner fill 100% of the width and height and all i do is toggle the z-index to switch which one is on top. In code this can translate to something like:

html.Div( # chart area
    children=[
        html.Div( # loading spinner
            id="chart-loader-div",
            style={
                'width': '100%',
                'height': '100%',
                'display': 'flex',
                'justify-content': 'center',
                'align-items': 'center',
                'font-size': '0px',
                'position': 'absolute',
                'z-index': '1031',
                'background': 'rgba(125, 125, 125, 0.0)',
            },
            children=[
                html.Div(
                    id="chart-loader",
                    children=[
                        html.Img(
                            id="chart-loader-spinner",
                            src="assets/spinner.gif",
                            style={
                                'display': 'block',
                                'width': '80px'
                            }
                        ),
                        html.Div(
                            id="chart-loader-trigger"
                        )
                    ]
                )
            ]
        ),
        dcc.Graph(
            id='main_plot',
            figure=px.bar(pd.DataFrame(columns=['x','y']),x='x',y='y')
        )],
    className='plot-object',
    style={
        'z-index': 10,
    }
),

Regarding the second part of the code, what im saying is that i found two ways to switch the z-index of the html elements:

  1. Return the style as an output of the clientside callback. It could be either from the graph or the chart-loader-div
    callbacks.py
dashapp.clientside_callback(
    ClientsideFunction(
        namespace='callback_spinner',
        function_name='hide_spinner'
    ), [
        Output('chart-loader-div', 'style'),
    ], [
        Input('foo', 'bar'),
    ]
)

callback_spinner.js

// i will ommit the setup to keep this message shorter
return [{
                'width': '100%',
                'height': '100%',
                'display': 'flex',
                'justify-content': 'center',
                'align-items': 'center',
                'font-size': '0px',
                'position': 'absolute',
                'z-index': -1,
                'background': 'rgba(125, 125, 125, 0.0)'
}]
  1. Access the html element directly from the clientside callback with the snippet:
document.getElementById("chart-loader-div").style['z-index'] = -1;

imho, the first option is the way to go because its more explicit (zen of python #2), but a one-line solution is very tempting

1 Like

got it going! this is extremely helpful, thank you and great job thinking outside the box here!!

okay, sorry but now I have one more question. I went with the first method as you suggested, which is hiding the loading bar once the graph loads. however, I would like this loading bar to show each time a user changes any of the dropdowns in the dashboard. do you have any suggestions here?

I do, but you might not like it hahaha
I imagine your callbacks are working like this:
changes_input → draw_graph → hide spinner
You will have to add an intermediate callback such as this:
changes_input → trigger_spinner → draw_graph → hide_spinner

So, instead of the input triggering drawing the graph, the input should trigger toggling the spinner, which then triggers building the chart

Also, your inputs will now have to enter the draw_graph callback as States now. More on States here

1 Like

This makes sense also but what would be the method to “trigger” the spinner?

just set the z-index of the spinner div to something high

got you. so i’ve been able to both show and hide the spinner in individual test scripts; however, i am now thinking this is not possible to do. mainly because dash currently does not allow for the same output to be mentioned in more than one callback, which would be the case here - mentioning the spinner style as an output for both showing and hiding.

sounds like you are far more well versed on this type of thing than i, so please correct me if i am wrong. either way, thanks for going above and beyond in answering my questions, on a thread where you posted the main question :smiley:

You can use the same callback for turning the spinner on and off. Something like:
callbacks.py

dashapp.clientside_callback(
    ClientsideFunction(
        namespace='callback_spinner',
        function_name='toggle_spinner'
    ), [
        Output('chart-loader-div', 'style'),
    ], [
        Input('foo', 'bar'),
    ], [
        State('chart-loader-div', 'data'), # Im using the data wildcard to store the spinner last state
    ]
)

callback_spinner.js

if(!window.dash_clientside) {window.dash_clientside = {};}
window.dash_clientside.callback_spinner = {
    toggle_spinner: function(_trigger, spinner_current_state) {
         return [
                'width': '100%',
                'height': '100%',
                'display': 'flex',
                'justify-content': 'center',
                'align-items': 'center',
                'font-size': '0px',
                'position': 'absolute',
                'z-index': spinner_current_state == 'visible' ? -1 : 1031, // gotta love those ternary operators in js 
                'background': 'rgba(125, 125, 125, 0.0)'
         ];
    }
}

Also, you will have to use the data argument from the chart-loader-div element as an Output in the ** changes_input and draw_graph callbacks

1 Like

bravo…

wow, your post got the first reply after 8 months… actually you solved the issue yourself and also helped the other to solve the issue. Bravo.

1 Like