Using dcc.Interval for continuous update

Hi awesome community,

I am trying to refresh my HTML content every moment using dcc.Interval but not being able to do that. I am following 2 - On the disk (e.g. on a file or on a new database) for sharing data between callbacks. Can anyone please help?

Way to reproduce

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
from dash.exceptions import PreventUpdate

from tempfile import gettempdir
from os import getpid
from os.path import join as pjoin

tmpfile= pjoin(gettempdir(), f'time-{getpid()}')

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Input(id='total', value=0),
    html.Div(id='page-content'),
    # the layout should update every 500 mili seconds
    dcc.Interval(interval=500)
])

@app.callback(Output('page-content', 'children'),
              [Input('total', 'value')])
def display_page(value):

    if not value:
        raise PreventUpdate

    value = int(value)-1
    with open(tmpfile, 'w') as f:
        f.write(str(value))

    with open(tmpfile) as f:
        value= int(str(f.read(value)))

    value+=1

    print(value)

    
    return html.Div([
        html.Plaintext(f'Value: {value}')
    ])


if __name__ == '__main__':
    app.run_server(debug=False)

Expected output

If 10 is typed on the box, it should print

Value 10
Value 9
Value 8


at a time every 500 mili seconds.

Actual output

Only Value 10 is printed and does not update

Is that because it’s took quick? if it updates every half a second, maybe it doesn’t have enough time to display the expected output?

1 Like

Hi @adamschroeder,

I tried with 1s, 3s, and 5s, didn’t help.

@adamschroeder, more info-- there is a print(value) statement in my code that should print a value on my console at every refresh/callback. However, I see only one print on my console-- the value I plugged in initially-- 10, which is why I believe dcc.Interval() is not doing the update it is expected to.

But I see that you’re only printing one value. The function doesn’t have a for loop that prints out multiple times. It’s just one print. You have to put the print and value+=1 inside a for loop, I think.

People already tried that but to no avail, please see Update layout after callback, without refreshing page?

Nevertheless, I tried you suggestion writing a recursive loop, but didn’t help. Even worse, it doesn’t get any callback now:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
from dash.exceptions import PreventUpdate

from tempfile import gettempdir
from os import getpid
from os.path import join as pjoin
from time import sleep

tmpfile= pjoin(gettempdir(), f'time-{getpid()}')

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Input(id='total', value=0),
    html.Div(id='page-content'),

    # the layout should update every 1000 mili seconds
    dcc.Interval(interval=1000)
])


@app.callback(Output('page-content', 'children'),
              [Input('total', 'value')])
def display_page(value):

    if not value:
        raise PreventUpdate

    value= int(value)

    if value>0:
        value-=1
        display_page(value)
        print(value)

    sleep(3)
    return  html.Div([
                html.Plaintext(f'Value: {value}')
            ])



if __name__ == '__main__':
    app.run_server(debug=False)

maybe somthing like this, @tbillah

@app.callback(Output('page-content', 'children'),
              [Input('total', 'value')])
def display_page(v):
    countdown = []
    v = int(v)
    while v > 0:
        print(v)
        sleep(3)
        countdown.append(v)
        v = v - 1

    return [html.Div([
                html.Plaintext(f'Value: {x}')
            ])
            for x in countdown
    ]
1 Like

Thanks @adamschroeder, I should have mentioned that I have already tried this. This approach will sleep 3 seconds to generate each element of the list countdown and then print all the lines together in the webpage:

Value: 10
Value: 9
Value: 8
Value: 7
Value: 6
Value: 5
Value: 4
Value: 3
Value: 2
Value: 1

But I was looking for ways to print one line at a time with 3 seconds delay in between.

Hey @tbillah

I think the interval needs to be in the callback as an input. How about something like this:

import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
from dash.exceptions import PreventUpdate

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(
    [
        "start value",
        dcc.Input(id="total", type="number", value=0),

        "countdown value", "  #can be hidden - or use dcc.Store instead
        dcc.Input(id="countdown", type="number", value=0, disabled=True),

        html.Div(id="page-content"),

        dcc.Interval(id="interval", n_intervals=0, interval=3000),
    ]
)


@app.callback(
    [Output("page-content", "children"), Output("countdown", "value")],
    [Input("total", "value"), Input("interval", "n_intervals")],
    [State("countdown", "value")],
)
def display_page(value, n, countdown):
    if not value:
        raise PreventUpdate

    ctx = dash.callback_context
    callback_input_id = ctx.triggered[0]["prop_id"].split(".")[0]

    if callback_input_id == "total":
        countdown = value
    elif countdown <= value and countdown >= 1:
        countdown -= 1

    output = html.Div([html.Plaintext(f"Value: {countdown}")])

    return output, countdown


if __name__ == "__main__":
    app.run_server(debug=True)

Edit: @Emil has a prettier counter :slight_smile:

2 Likes

You must add the Interval component as an Input to trigger the callback. Here is a piece of example code based on your example,

import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
from dash.exceptions import PreventUpdate

app = dash.Dash(__name__, prevent_initial_callbacks=True,
                external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css'])
app.layout = html.Div([
    dcc.Input(id='total', value=0), html.Div(id='page-content'),
    dcc.Interval(interval=1000, id="trigger"), dcc.Store(id="store", data=0)
])


@app.callback([Output('page-content', 'children'), Output('store', 'data')],
              [Input('total', 'value'), Input('trigger', 'n_intervals')], [State('store', 'data')])
def display_page(value, n_intervals, counter):
    # Stop when we get to zero.
    if not value or int(value) < 1:
        raise PreventUpdate
    # Set copy value to counter, when input text changes, decrement otherwise.
    input_id = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
    counter = max(int(value) if input_id == "total" else int(counter) - 1, 0)
    # Update the page and the counter.
    return html.Div([html.Plaintext(f'Value: {counter}')]), counter


if __name__ == '__main__':
    app.run_server(debug=False)

EDIT: @ AnnMarieW was faster :slightly_smiling_face:

3 Likes

Thank you very much @AnnMarieW and @Emil, using your suggestion about introducing n_intervals as an input to the callback function, I recreated my original module to produce the desired counter:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import logging
# from time import sleep
from tempfile import gettempdir
from os import getpid, remove
from os.path import join as pjoin, isfile
from dash.exceptions import PreventUpdate

tmpfile= pjoin(gettempdir(), f'time-{getpid()}')
if isfile(tmpfile):
    remove(tmpfile)

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
log= logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)

app.layout = html.Div([
    dcc.Input(id='total', value=0, type='number'),
    html.Div(id='page-content'),
    # the layout should update every 1000 mili seconds
    dcc.Interval(id= 'interval', n_intervals=0, interval=1000)
])


@app.callback(Output('page-content', 'children'),
              [Input('total', 'value'), Input('interval', 'n_intervals')])
def display_page(value, n_intervals):

    if isfile(tmpfile):
        # read value from file
        with open(tmpfile) as f:
            value = f.read()
    else:
        # value is obtained from Input
        pass


    if not value or int(value)<1:
        if isfile(tmpfile):
            # remove the tmpfile so the counter can be reused
            remove(tmpfile)
        raise PreventUpdate

    value= int(value)
    value-= 1

    # write value back
    with open(tmpfile, 'w') as f:
        f.write(str(value))


    # return value
    return html.Div([
        html.Plaintext(f'Value: {value}')
    ])

if __name__ == '__main__':
    app.run_server(debug=False)

As I noted in my description, I used a temporary file to share data between callbacks instead of using dcc.State

Hello all, first post so I hope I have the etiquette right?

Any help is appreciated.
I think I am trying to do something similar to the above but have had limited success.
I have 3 charts and a table on my page, and I have the charts updating via intervals (success!).
I’d like to get the table on an interval as well, preferably a different one, but have failed.
So I thought I’d put the whole page on 1 common interval and failed in that.
Here is my code (mostly gutted):

# -*- coding: utf-8 -*-
# external deps
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
import dash_table
import plotly.graph_objects as go
from dash_table.Format import Format, Group, Scheme, Symbol

# internal dependencies
import dataparser #prepares data
import chart1

# global vars
Fcolor = 'rgba(0,0,0,0)'
Fsize = 10
Host = '0.0.0.0'
port = 9999
updatePeriod = 30*1000, # in milliseconds

data = {} 
dfTable = []

external_css = ["styling.css"]
app = dash.Dash(__name__, external_stylesheets=external_css)

timestamp = "Updated: "+dt.datetime.now().strftime("%H:%M %d/%m/%y")+"(GMT)"

def colors(col):
    #create palette
    return colorlist

def get_banner():
    #create banner
    return banner

def create_charts(df1, figure2):
    global dfTable
    # do some math, create dfTable
    
    global data
    data = df1

    banner = get_banner()
        
    app.layout = html.Div(style={'backgroundColor': colors['background']}, children=[
                        html.Div(id="page-content"),
                        dcc.Interval(id="interval", n_intervals=0, interval=updatePeriod),
        
    #Create Header Region
                        html.Div([
                            html.Div([banner], style={'color': 'red'},),
                            html.Marquee([banner], style={'color': 'orange'})
                                 ], className="row"),
                        html.Header([html.Img(src='./Header.jpg')], style={'height': '33%', 'width': '10%'},),
                        html.Div(timestamp, style={'color': Fcolor, 'font-size':Fsize, 'margin-left':'88%'}),
                        html.Br(),
                        dcc.SayHello(id='say-hello',
                                    children=html.Div(['Hola!',html.Br(),]),
                                    style={'width': '10%', 'height': '60px'},
                                    ),
                        html.Div(id='output-data-upload'),
    ######################################################Graphs & Charts
                        dash_table.DataTable(id='datatable',
                            columns = tablecols,
                            style_as_list_view=True,
                            data=dfTable.to_dict('records'),
                            sort_action="native", page_action='none',
                            row_selectable="", row_deletable=False, selected_row_ids=[0], 
                            active_cell={'row_id': dfTable['id'][0], 'row': 0, 'column': 0, 'column_id': 'NAME'},
                            style_table={'height': '270px', 'width':'83%'},
                            fixed_rows={'headers': True}
                        ),
                        html.Div(id='datatable-container'),
    #############################banner
                        html.Div("Thank You!", style={'font-style': 'italic'}),
                        html.Div(id='XYZ'),
                        html.Br(),
                        html.Div([
                            html.Div([dcc.Graph(id='graph1'),
                                      dcc.Interval(id='intervalG1', interval=updateTime, n_intervals=0
                                    ),
                                     ], className="half"),
                            html.Div([dcc.Graph(id='graph2'),
                                      dcc.Interval(id='intervalG2', interval=updateTime, n_intervals=0
                                    ),
                                     ], className="half"),
                                 ], className="row"),
                        html.Br(),
    ################################### bubble2/animation
                        html.Div([
                            html.Div([dcc.Graph(figure=figure2)], className="full"),
                                 ], className="row"),
    ####################################Footer
                        html.Footer("2021", style={'textAlign':"center"})
                    ], className="row")
    #________________________________________________________________________________
@app.callback(
            [Output('page-content', 'children')],
            [Input('interval', 'n_intervals')],
            )

@app.callback(
            Output('XYZ', 'children'),
            [Input('upload-data', 'contents')],
            [State('upload-data', 'files')]
            )
def parse_contents(contents, files, date):
    if contents is not None:
        #check files
        return html.Div(['All done.'])
    if filename is None: return
    
@app.callback(
            [Output('graph1', 'figure'), Output('graph2', 'figure')],
            [Input('datatable', 'selected_row_ids'),Input('datatable', 'active_cell'), Input('intervalG1', 'n_intervals'), Input('intervalG2', 'n_intervals')]
             )
def update_figures(selected_row_ids, active_cell, n, m):
    #create figures
    return figtrace1, figtrace2


if __name__ == '__main__':
    df1 = dataparser.getinfo()
    figure2 = chart1.render()
    create_charts(df1, figure2)
    app.run_server(debug=False, use_reloader=True, port=port, host=Host)

This callback has no function, you need to add a function and the return to the Output.

The dcc.Interval can be placed in any part of the layout, the function is to change the n_intervals component periodically, then if you use n_intervals as Input in any callback, the function of the callback will be executed everytime the component change, so you can use different dcc.Intervals for different callbacks.

1 Like

Ahhh I see, ok I will give this a shot. Thanks!

1 Like

Works! I did as you suggested.

Hi Emil,

Thanks a lot for this code - it was very helpful.

I was wondering if there is a way to not show the dialog box with the counter on the frontend. I could not figure that out.

Also, can you use this same approach for updating tables instead of the entire page? Or would you suggest using a different approach for that?

Yes, you can. Then you just target the table as output, thereby also avoiding showing the counter on the front end directly :slight_smile:

1 Like

That could work.

I tried to get rid of ‘dcc.Input’ and to move ‘page-content’ and ‘store’ to the two tables, but as soon as I did that, the tables stop showing up.

Error: KeyError: ‘…page-content.children…store.data…’

Can you not have multiple ‘children’ in each table or could it be something else?