Server side updation along with interval component

I’m creating an app which will retrieve the data from my PostgreSQL database every 30 seconds and update my live chart and send toast notification to all users. But the problem is when users load their respective windows the served data is a bit different, all the users receive the data and alert at different interval instead of a fixed 30 seconds interval. I’m thinking this is because interval component works on client side and every user triggers the interval at different time when they open the page. I also looked at the forum and found couple of helpful links using server side updation instead of interval component, but couldn’t get it working to my needs. Here is the link to the dash topic server side update. The code here is working but it is not refreshing the chart on its own. I have to reload the page for new data. I also tried putting the interval component along with ProcessPoolExecuter(max_workers=1) but interval component is not working. Maybe I’m missing something here and community can help me out with the same.

To reproduce the same:

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

import dash
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
import numpy as np

# number of seconds between re-calculating the data
UPDADE_INTERVAL = 5


def get_new_data():
    print("get_new_data")
    """Updates the global variable 'data' with new data"""
    # global data
    data = np.random.normal(size=1000)
    np.save("data1", data)


def get_new_data_every(period=UPDADE_INTERVAL):
    print("get_new_data_every")
    """Update the data every 'period' seconds"""
    while True:
        get_new_data()
        print("data updated")
        time.sleep(period)


def make_layout():
    data = np.load("data1.npy")
    chart_title = "data updates server-side every {} seconds".format(UPDADE_INTERVAL)
    return html.Div(
        [
            dcc.Graph(
                id="chart",
                figure={
                    "data": [go.Histogram(x=data)],
                    "layout": {"title": chart_title},
                },
            ),
            dcc.Interval(
                id="interval-component",
                interval=10 * 1000,
                n_intervals=0,  # in milliseconds
            ),
        ]
    )


app = dash.Dash(__name__)

# get initial data
get_new_data()

# we need to set layout to be a function so that for each new page load
# the layout is re-created with the current data, otherwise they will see
# data that was generated when the Dash app was first initialised
app.layout = make_layout


@app.callback(Output("chart", "figure"), [Input("interval-component", "n_intervals")])
def update_trace(n_intervals):
    data = get_new_data_every()
    print(data)
    figure = {"data": [go.Histogram(x=data)], "layout": {"title": "chart_title"}}
    return figure


def start_multi():
    executor = ProcessPoolExecutor(max_workers=1)
    executor.submit(get_new_data_every)


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

If you can also show me how to modify my code to send toast notifications to all the users when data gets updated along with automatic refresh of the charts that would be a huge help too.

Thanking you in anticipation!

python --version: 3.8
dash.version:1.20.0
dcc.version:1.16.0

A quick update, I was able to run the above code using @chriddyp and @nedned advice of using python scheduler function, but with a tweak. Instead of using BlockingScheduler i used BackgroundScheduler. The app is now running on port 8050 using Flask server. I’m serving the app using waitress(working on windows!). But the other problem still persists that all the clients are not receiving the data at the same exact time. They are getting the new data based on the interval timer associated with their sessions.

I have attached a gif for your understanding, here you can see all the sessions are getting updated at different time. What I want is to get them updated at the same exact time.(all of them should receive the new data at the same exact time and charts should be refreshed.)
dashboard

Code to reproduce the same:

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
#from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.schedulers.background import BackgroundScheduler

import dash
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
import numpy as np

from flask import Flask
from waitress import serve

# number of seconds between re-calculating the data
UPDADE_INTERVAL = 20


def get_new_data():
    print("get_new_data")
    """Updates the global variable 'data' with new data"""
    # global data
    data = np.random.normal(size=1000)
    np.save("data1", data)


sched = BackgroundScheduler()


# @sched.scheduled_job("interval", seconds=30)
# def timed_job():
#     get_new_data()
#     print("saved new data")

sched.add_job(get_new_data, "interval", seconds=10)
sched.start()

# def get_new_data_every(period=UPDADE_INTERVAL):
#     print("get_new_data_every")
#     """Update the data every 'period' seconds"""
#     while True:
#         get_new_data()
#         print("data updated")
#         time.sleep(period)


def make_layout():
    data = np.load("data1.npy")
    chart_title = "data updates server-side every {} seconds".format(UPDADE_INTERVAL)
    return html.Div(
        [
            dcc.Graph(
                id="chart",
                figure={
                    "data": [go.Histogram(x=data)],
                    "layout": {"title": chart_title},
                },
            ),
            dcc.Interval(
                id="interval-component",
                interval=10 * 1000,
                n_intervals=0,  # in milliseconds
            ),
        ]
    )


application = Flask(__name__)
app = dash.Dash(__name__, server=application)

# get initial data
get_new_data()

# we need to set layout to be a function so that for each new page load
# the layout is re-created with the current data, otherwise they will see
# data that was generated when the Dash app was first initialised
app.layout = make_layout


@app.callback(Output("chart", "figure"), [Input("interval-component", "n_intervals")])
def update_trace(n_intervals):
    data = np.load("data1.npy")
    print(data)
    figure = {"data": [go.Histogram(x=data)], "layout": {"title": "chart_title"}}
    return figure


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

To run using waitress use: waitress-serve --listen=*:8050 serversideupdate:app.server where serversideupdate is your python file and app is instantiation of dash server using flask.

Any help would be appreciated!. Thank you.

One can also solve this problem using dcc.Store component.

  1. Get the data.
  2. store it in dcc.Store as output.
  3. use the dcc.Store as input to update the chart.

Here is some documentation: Store | Dash for Python Documentation | Plotly

With this approach, you are storing data client side, so I don’t believe you are guaranteed that the updates for multiple sessions are in sync.

A Single user with multiple sessions open will be alerted at the same time, using the dcc.Store
For different users on different machine i’ll suggest to use websocket.

  1. Create Websocket element using dash_extensions.
  2. update the graph using the websocket as input.

Thank you for noticing. Do you have any other solution in mind to update different users on different machines at the same time @Emil ?

Ah, yes, I guess that might be true (maybe depending on the Store configuration?). I was thinking in general, i.e. across different browsers, devices etc. For a general solution I can think of two options,

  • Scheduling server side updates, poll client updates via an Interval component (i.e. your previous approach). I think this is a good approach for most cases
  • Push the data to the client via WebSocket (or ServerSideEvent component). This solution is a bit more complex, but it enables faster updates