dash
dash copied to clipboard
New features for dcc.Loading
This PR adds features to dcc.Loading
- adds
delay_show
anddelay_hide
props to prevent flickering when the loading spinner shows only for a short time - adds
overlay_style
prop so you can do things like make children visible during loading and add opacity. - adds
target_components
prop to specify which component/props can trigger the loading spinner - adds
custom_spinner
prop so you can provide your own spinner rather than using the built-in options - refactored component to functional instead of a class
Closes:
https://github.com/plotly/dash/issues/951
#2147 will be fixed by using delay_show
#1922 will be fixed by using target_components
#1802 will be fixed by using delay_show
#2422 will be fixed by using delay_show
#879 will be possible by using custom_spinner
and overlay_style
#736. can't replicate the issue
Closes from the old dcc repo
https://github.com/plotly/dash-core-components/issues/873 - possible with custom_spinner
and delay_show
https://github.com/plotly/dash/issues/1541 - can't replicate the issue
Contributor Checklist
-
[ ] To Do / Questions
-
[x] Controlling the visibility of the component being loaded with the opacity and background color of the spinner would be a breaking change. Probably need to find a better way to make it possible to add opacity to the component . Update: added
overlay_style
prop. -
[ ] Add ability to manually trigger loading as requested in #2696
-
[ ] Remove the
show_initially
prop? This was from the dbc library but it seems to have no effect. The spinner will briefly flash on page load, even whenshow_initially=False
-
[ ] Would setting a
delay_hide
time solve the issue where there is a lag between the callback finishing and a figure rendering with large data sets? Or might that time be too variable? If this is a solution, I'd need to update thedelay_hide
timer because it currently sets a minimum time for the timer to display rather than extending the display time. See https://github.com/plotly/dash/issues/2690
-
-
[ ] I have run the tests locally and they passed.
-
[ ] I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR
-
[ ] I have added entry in the
CHANGELOG.md
-
[ ] If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follows
- [ ] this GitHub #PR number updates the dash docs
- [ ] here is the show and tell thread in Plotly Dash community
Example 1 delay_hide
and delay_show
prop
This callback runs for 500ms. This example shows
- How to add a delay between when the loading starts and when the spinner is displayed. In this example, the callback takes 500ms, so we set the
delay_show=600
so the spinner is not displayed. This prevents annoying flashes for callbacks with very fast loading times such as figure updating withhoverData
. - How to set a minimum time for the spinner to be displayed once loading starts - in this case 2000ms
A good demo for updating with hoverData
is to use this example in the docs Try it with adding dcc.Loading([<example app layout> ], delay_show=500)
import time
import dash
from dash import Dash, Input, Output, State, html, dcc
app=Dash()
app.layout = html.Div(
[
html.Button("Load", id="loading-button", n_clicks=0),
html.Div("delay_show (ms)"),
dcc.Input(type="number", value=0, id="delay-show", debounce=True),
html.Div("delay_hide (ms)"),
dcc.Input(type="number", value=0, id="delay-hide", debounce=True),
html.Hr(),
dcc.Loading(html.Div(id="loading-output"), id="loading"),
]
)
@app.callback(
Output("loading-output", "children"),
Input("loading-button", "n_clicks"),
)
def load_output(n):
if n:
time.sleep(.5)
return f"Output loaded {n} times"
return "Output not reloaded yet"
@app.callback(
Output("loading", "delay_show"),
Output("loading", "delay_hide"),
Input("delay-show", "value"),
Input("delay-hide", "value")
)
def update_delay_show_hide(show, hide):
if show is None or hide is None:
return dash.no_update
return int(show), int(hide)
app.run(debug=True)
Example 2 target_components
prop
Demo of how to overrides default Loading behavior if target_components
is set. By default, Loading fires when any child element enters loading state. This makes loading opt-in: Loading animation only enabled when one of target components enters loading state.
import time
import dash
from dash import Dash, Input, Output, State, html, dcc
app=Dash()
app.layout = html.Div(
[
html.Button("Load div 1", id="loading-button1", n_clicks=0),
html.Button("Load div 2", id="loading-button2", n_clicks=0),
html.Hr(),
dcc.Loading([
html.Div(id="loading-output1"),
html.Div(id="loading-output2"),
], target_components=[{"loading-output1": "children"}]),
]
)
@app.callback(
Output("loading-output1", "children"),
Input("loading-button1", "n_clicks"),
)
def load_output(n):
if n:
time.sleep(2)
return f"Output loaded {n} times. This callback triggers the loading spinner"
return "Callback 1 output not reloaded yet"
@app.callback(
Output("loading-output2", "children"),
Input("loading-button2", "n_clicks"),
)
def load_output(n):
if n:
time.sleep(.5)
return f"Output loaded {n} times. No loading spinner"
return "Callback 2 output not reloaded yet"
app.run(debug=True)
Example 3 Styling with overlay_style
prop
Default: content is hidden while loading
Styled with overlay_style={"visibility":"visible", "opacity": .5, "backgroundColor": "white"}
This keeps the content visible while loading and adds opacity
import time
import dash
from dash import Dash, Input, Output, State, html, dcc
import plotly.express as px
data_canada = px.data.gapminder().query("country == 'Canada'")
app=Dash()
app.layout = html.Div(
[
html.Button("Start", id="loading-button", n_clicks=0),
html.Hr(),
dcc.Loading(
[dcc.Graph(id="loading-output", figure=px.line(data_canada, x="year", y="pop"))],
overlay_style={"visibility":"visible", "opacity": .5, "backgroundColor": "white"},
color="red"
),
]
)
@app.callback(
Output("loading-output", "figure"),
Input("loading-button", "n_clicks"),
)
def load_output(n):
if n:
time.sleep(1)
return px.bar(data_canada, x="year", y="pop")
return dash.no_update
app.run(debug=True)
Example 4 custom_spinner
prop
Instead of using one of the provided spinner component, you can provide your own.
A custom_spinner
can be used to remove/hide the spinner. This is nice when using nested dcc.Loading components.
It's possible to create a component with just text or other Dash components and no spinner.
This example uses:
custom_spinner=html.H2(["My Custom Spinner", dbc.Spinner(color="danger")])
import time
import dash
from dash import Dash, Input, Output, State, html, dcc
import plotly.express as px
import dash_bootstrap_components as dbc
data_canada = px.data.gapminder().query("country == 'Canada'")
app=Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = dbc.Container(
[
dbc.Button("Start", id="loading-button", n_clicks=0),
html.Hr(),
dcc.Loading(
[dcc.Graph(id="loading-output", figure=px.line(data_canada, x="year", y="pop"))],
overlay_style={"visibility":"visible", "opacity": .5, "backgroundColor": "white"},
custom_spinner=html.H2(["My Custom Spinner", dbc.Spinner(color="danger")])
),
]
)
@app.callback(
Output("loading-output", "figure"),
Input("loading-button", "n_clicks"),
)
def load_output(n):
if n:
time.sleep(1)
return px.bar(data_canada, x="year", y="pop")
return dash.no_update
app.run(debug=True)
@AnnMarieW Could you give an example of when target_component is used?
delay_show and delay_hide seem reasonable and the implementation makes sense to me.
@AnnMarieW This is really exciting, the loading opacity update is really clever, and the demonstrations are really helpful to understand the scope of these updates.
@AnnMarieW re: 'mode' prop-
What you implemented matches the Loading behavior that I'd like to have.
I'd consider changing the name, however. 'mode' seems like a execution context rather than 'on or off.' I'd perhaps use 'mode' to be 'auto' or 'manual' and 'manual' would disable targeted loading state and consideration of the loading state of children, and instead would rely on another prop like 'setLoading' or something.
This would clarify that the Loading component is either driven by state that can be inferred from the renderer (those components whose state are waiting for backend information) or directly specified by the user (indirectly related components, like a search option that should be put into a loading state while something else is executing).
@JamesKunstle I'm not sure what you mean re the mode prop. Can you clarify or supply some code?
@AnnMarieW Yes certainly:
So mode
prop could just be manual-mode
and be a boolean, True or False.
When not in manual-mode
, the Loading component behaves exactly as it does now, changing as an effect of other components waiting on a response from the backend.
In manual
mode, the Loading component would honor the additional prop set-loading
(naming not final), a boolean that would be under full manual control from the user.
if (manual_mode) {
setShowSpinner(set_loading);
return;
}
Here's a concrete example:
Imagine that I have a dcc.Interval component that fires every 5 seconds, making a request to the backend to check whether an asynchronous resource is available. Another component, like a Dropdown, indirectly relies on the availability of that resource, so it should be in a forced Loading state that stays enabled even when the dcc.Interval is waiting to fire again.
If I could use manual mode, a final successful return from the callback that handles the Interval logic could disable loading for the Dropdown.
This way, Loading becomes a developer-controllable state that is managed by application logic rather than by the dash renderer.
re: mode
- agreed that the name is a little ambiguous, but I do like having a single prop with three values, rather than a second prop that only applies to one of the values of the other.
What about borrowing from CSS: display: "show" | "hide" | "auto"
?
Re: api for enabling/disabling, what feels most intuitive to me is disabled
for consistency with dcc.Interval
and the input components, and also it's a more transparent name -- doesn't lend itself super easily to 3 values though.
Re: api for enabling/disabling, what feels most intuitive to me is
disabled
for consistency withdcc.Interval
and the input components, and also it's a more transparent name -- doesn't lend itself super easily to 3 values though.
That, for me, feels ergonomically less nice for the same reason that it does for dcc.Interval - negating 'disabled' in my head to set the correct boolean value takes a few more cycles than I want, and I normally create global variables to solve it, like "INTERVAL_DISABLED" = True, "INTERVAL_ENABLED" = False, etc.
re:
mode
- agreed that the name is a little ambiguous, but I do like having a single prop with three values, rather than a second prop that only applies to one of the values of the other.What about borrowing from CSS:
display: "show" | "hide" | "auto"
?
Passing a string parameter for 'display' seems reasonable
Ok great, let’s go with display: "show" | "hide" | "auto"
Ok great, let’s go with
display: "show" | "hide" | "auto"
Done
I can work on the docs PR next. Note that I can't merge PRs here, so can you do that for me?
Super excited for these!
I can work on the docs PR next. Note that I can't merge PRs here, so can you do that for me?
Can we hold on merging while I work on a fix for #2775
Here's the PR for the docs https://github.com/plotly/ddk-dash-docs/pull/2570
Hello AnnMarieW,
thank you very much for implementing the features delay_show and delay_hide to dcc.Loading. Do I have to update dash to have access to it?
Thank You very much in Advance
TypeError: The
dcc.Loading
component (version 2.14.1) received an unexpected keyword argument:delay_show
Allowed arguments: children, className, color, debug, fullscreen, id, loading_state, parent_className, parent_style, style, type
dash==2.14.1 dash-bootstrap-components==1.5.0 dash-core-components==2.0.0
After updating dash:
TypeError: The dcc.Loading
component (version 2.16.1) received an unexpected keyword argument: delay_show
Allowed arguments: children, className, color, debug, fullscreen, id, loading_state, parent_className, parent_style, style, type
Hi @schultz77
Thanks! I’m looking forward to it too 🙂. This will be available in the next release (Dash 2.17)
Ok! Thank You for your quick reply. In the meantime I'll keep using dbc.Spinner...