Dash ._dash_loading_callback positioning of spinner?

Hi,

I try to use a spinner for some graphs which contain lots of data retrieved from an sql database, hence they need some time to load. Using the ._dash_loading-callback css style I can manage to use a simple css spinner (see code below). The usage of the new [data-dash-is-loading=“true”] element seems not to do what I want, as it controls the styles while the input is active (e.g. see this post loading-states-api-and-a-loading-component-prerelease where you also find the code for the dash app, slightly altered)

I am so fare satisfied with the possibility to have a spinner working, but the spinner is positioned at the bottom of the page and not where I would expect it. Actually I would appreciate if the spinner would replace the element which is actually updated. Using the example code below, you can observer the following behavior:

  • On page load, the spinner is below the tabs where it is expected to be
  • when switching tabs the spinner is shown below the graph (and does not replace the graph)

My question is, if there is a method to make the spinner replace the to be updated element(s)?

Example code: dash_server_with_spinner.py

# -*- coding: utf-8 -*-
import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
import time

from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)

app.scripts.config.serve_locally = True
app.title = "Testing css spinner"

app.layout = html.Div(
    children=[
        html.Div(
            children = [
                html.H4('Example loading tabs with own css (class) style'),
            ]
        ),
    dcc.Tabs(
            id="tabs",
            value="tab-1",
	children=[
                dcc.Tab(label="Bar Graph", value="tab-1"),
                dcc.Tab(label="Scatter Graph", value="tab-2"),
	]),
	html.Div(id='tabs-content'),
    ],
)

@app.callback(Output('tabs-content', 'children'),
    [Input('tabs', 'value')])
def render_content(tab):
    time.sleep(2)  # allow time to see spinner
    if tab == 'tab-1':
        _layout = html.Div(id='loading-1', children=[
            dcc.Graph(
                id='graph-2-tabs',
                figure=go.Figure(
                    data=[
                        go.Bar(
                            x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                               2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                            y=[219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
                               350, 430, 474, 526, 488, 537, 500, 439],
                            name='Rest of world',
                            marker=go.bar.Marker(
                                color='rgb(55, 83, 109)'
                            )
                        ),
                        go.Bar(
                            x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                               2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                            y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156, 270,
                               299, 340, 403, 549, 499],
                            name='China',
                            marker=go.bar.Marker(
                                color='rgb(26, 118, 255)'
                            )
                        )
                    ],
                    layout=go.Layout(
                        title='US Export of Plastic Scrap',
                        showlegend=True,
                        legend=go.layout.Legend(
                            x=0,
                            y=1.0
                        ),
                        margin=go.layout.Margin(l=40, r=0, t=40, b=30)
                     )
                ),
            )
        ])
    elif tab == 'tab-2':
        _layout = html.Div(id='loading-2', children=[
            dcc.Graph(
                id='graph-1-tabs',
                figure=go.Figure(
                    data=[
                        go.Scatter(
                            x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                               2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                            y=[219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
                               350, 430, 474, 526, 488, 537, 500, 439],
                            name='Rest of world',
                            marker=go.Marker(
                                color='hotpink'
                            )
                        ),
                        go.Scatter(
                            x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                               2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                            y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156, 270,
                               299, 340, 403, 549, 499],
                            name='China',
                            marker=go.Marker(
                                color='gold'
                            )
                        )
                    ],
                    layout=go.Layout(
                        title='US Export of Plastic Scrap',
                        showlegend=True,
                        legend=go.layout.Legend(
                            x=0,
                            y=1.0
                        ),
                        margin=go.layout.Margin(l=40, r=0, t=40, b=30)
                    )
                ),
            )
        ])
    print(_layout)
    return _layout	
	
if __name__ == "__main__":
    app.run_server(debug=False)

The css file (to be places in the assets folder) is typography.css:

/* simple circular css spinner
 * 
 * why does this one needs to be placed in ._dash-loading-callback
 * and not in ._dash-loading-callback:after as the other spinners?
 *
 * Change size with setting fontsize, and position with margin
*/
._dash-loading-callback {
  margin: 60px auto;
  width: 7em;
  height: 7em;
  font-size: 7px;
  position: relative;
  border-radius: 50%;
  border-top: 1.1em solid rgba(0, 0, 0, 0.2);
  border-right: 1.1em solid rgba(0, 0, 0, 0.4);
  border-bottom: 1.1em solid rgba(0, 0, 0, 0.6);
  border-left: 1.1em solid #abc432;
  -webkit-transform: translateZ(0);
  -ms-transform: translateZ(0);
  transform: translateZ(0);
  -webkit-animation: load_spinner 0.95s infinite linear;
  animation: load_spinner 0.95s infinite linear;
}

@-webkit-keyframes load_spinner {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

@keyframes load_spinner {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}