Support for websockets

quick question because i can see it has been answered here and there.
From what i understand, Dash doesnt support websocket yet. where the limitation comes from, is it something plotly is working on?
I am thinking a dashboard connecting websocket of market data feeds and displaying live graphs. thanks

1 Like

Why do you need web sockets specifically? If you are getting market data you could just use an API with a wrapper such as AlphaVantage & https://github.com/RomelTorres/alpha_vantage

There’s nothing stopping you using a websocket library within your dash app for making connections. Unless you mean for communicating the various Dash API calls?

What i have in mind is a websocket for live market data and showing a grid with live updates. then on each update, a portfolio of options is recalculated and the result is shown in the grid (value and greeks).
Or a graph is updated with price updates.
Websockets are the standard in digital asset exchange now.

django-plotly-dash has a Pipe component that uses websockets to provide push notifications into Dash applications. Documentation here.

This might provide you with what you want already, although just using an Interval component to poll your source will probably turn out to be a better approach as it will remove the need to deal with throttling or otherwise managing the rate of updates.

1 Like

Sorry to revive an old thread, has there been any development progress towards web socket support?

There is even advocacy for it from plot.ly employees:

It seems like switching from Flask to Quart might make the mountain somewhat easier to climb https://gitlab.com/pgjones/quart.

2 Likes

There has not been any progress and this isn’t in the roadmap right now. It might be in the future.

inspired by this post, I decided to give it a try. Here is minimal example of how to solve this problem

import time

import dash
import dash_html_components as html
from flask_socketio import SocketIO


app = dash.Dash(__name__)
server = app.server
server.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(server)

@socketio.on('welcome')
def handle_message(message):
    print(str(message))

app.index_string = '''
<!DOCTYPE html>
<html>
    <head>
        {%metas%}
        <title>{%title%}</title>
        {%favicon%}
        {%css%}
        <script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js" integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=" crossorigin="anonymous"></script>
        <script type="text/javascript" charset="utf-8">
            
            var socket = io();
            
            socket.on('connect', function() {
                socket.emit('hello', {data: 'connected'});
            });

            socket.on('update', function(data) {
                document.getElementById('finish').textContent=data+'/10';
            });
            
        </script>
    </head>
    <body>
        {%app_entry%}
        <footer>
            {%config%}
            {%scripts%}
            {%renderer%}
        </footer>
    </body>
</html>
'''

app.layout = html.Div([
    'Hello!',
    html.Button('Click me', id='trigger'),
    html.H1('', id='finish')
])


@app.callback(
    dash.dependencies.Output('finish', 'children'),
    [dash.dependencies.Input('trigger', 'n_clicks')])
def countdown(click):
    
    if not click:
        raise dash.exceptions.PreventUpdate()
    
    for i in range(10):
        socketio.emit('update', i)
        time.sleep(0.3)
    
    return click

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

5 Likes

Here is minimal example of how to solve this problem

Nice work!

1 Like

I agree. Quart is Flask API compatible and supports (most, if not almost all) Flask Extensions. It seems to be the best choice considering current ASGI application frameworks.

1 Like

I have a use case for a pub/sub receiver so my dashboard ONLY updates when there is new data from the data generators chosen by the user. Although this can be done by polling (current methods) - with many users and many publishers - it requires an interim step to Redis to make the data available to all the users. It also requires a LOT of polling to keep lag times down.

A react (pub)sub receiver (requiring a WebSocket connection) could trigger a callback and update just the data elements that have changed.

Thinking about modern dashboards - this could be well suited to some use cases.

Ian

Inspired by the above post from @marcin I decided to come up with a solution using flask_socketio which does not need to set index_string but uses Dashs external_scripts parameter to load socketio. The actual worker script can be added to the assets directory where it gets loaded automatically by Dash. I find this makes the implementation very clean.

app.py

import dash
import gevent
from flask_socketio import SocketIO
import dash_core_components as dcc
import dash_html_components as html

n = 0

external_scripts = [
    {
        "src": "//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js",
        "integrity": "sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=",
        "crossorigin": "anonymous",
    }
]

app = dash.Dash(__name__, external_scripts=external_scripts)
socketio = SocketIO(app.server)


@socketio.on("request update")
def connect():
    global n
    n += 1
    socketio.emit("update", data=n)


app.layout = html.Div(id="root_div", children=[
    dcc.Graph("graph"),
])


socketio.run(app.server, port=8050, debug=True)

assets/custom.js:

var socket = io();
var x = [];
var y = [];


var timer = setInterval(myTimer, 100);


function myTimer() {
    socket.emit("request update");
}


socket.on("update", function(n) {
    x.push(n);
    y.push(n**2);
    var graph = document.getElementById("graph");
    Plotly.newPlot(
        graph,
        data=[
            {x: x, y: y},
        ]
    );
})

This results in a graph that updates every 100ms by requesting new data from the server. Big plus: No annoying change of the title to “Updating…” :slight_smile:

5 Likes

Wait what? This is amazing!

I’m trying this out this afternoon! Awesome job! I have to confess I didn’t even know there was a flask socket option!

This may open the door to pub/sub which is my eventual goal for dashboards with intermittent data.

Big step for Dash IMHO

Thanks!

1 Like

Thank you for sharing stlehman!
This is what I was looking for.
In my current app I use .interval function to update a field in the client with Redis in between.
My concrete application is to update a field when a new MQTT msg arrives.

I try to implement your solution as follows, without success:
pip3 install gevent
pip3 install flask-socketio

Create a folder named /assets in the dir where my app.py lives.
Create a file custom.js inside that folder, containing your code.
Create an app.py , containing your code.

I run apache2, and wsgi that calls app.py

If I navigate to my webaddress I get nothing.

If I put a dash example code there, it runs as long as I follow Sentdex and put this line in, right a bove the if --name-- = statement:
server = app.server

Excuse my ignorance, I am a newbee in this realm.
I would like to get this to work because it seems to be a more scalable and reliable solution then .interval/Redis.

Any suggestions of where I am wrong are very welcome!

@kkoen Sorry for the late reply. I must confess it is hard to follow your example because it is missing the actual code you are using. If you add it (with proper formatting for readability :wink: ) I could have a look at it and tell you my thoughts.

Here are some additional thoughts on my above post.

I had a closer look on how Dash works and I can not generally recommend the approach I took here. This is mainly because it bypasses the Plotly React component and directly manipulates the underlying Plotly graph component. It actually works but it rips you of many benefits that Dash offers (event handling, plain Python code and statelessness).

I think it is best practice to stick to the Dash approach and only use Websockets if absolutely necessary (for fast live-data). And then use a plain Javascript Plotly component embedded in a Div to clearly separate it from the rest of the Dash app.

Also with the update_title parameter you can now prevent Dash from updating the page title during refresh which was the reason that led me to the Websockets approach in the first place.

For the people who are trying this block of code, pip install Flask==1.0.0

I tried to create a websocket dash component, basically it is a wrapper of https://github.com/theturtle32/WebSocket-Node. I shared the detail steps in https://jackylishi.medium.com/build-a-realtime-dash-app-with-websockets-5d25fa627c7a

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { w3cwebsocket as W3CWebSocket } from "websocket";

/**
 * DashWebsocket is an adapter for websocket.
 * It takes two property, `url` and 'msg'
 * `url` indicates the websocket url
 * `msg` display the message returns from webscoket
 */
export default class DashWebsocket extends Component {
    componentDidMount() {
        const url = this.props.url;
        const client = new W3CWebSocket(url);
        client.onopen = () => {
            console.log('websocket connected');
        }
        client.onmessage = (message) => {
            this.props.setProps({ msg: message.data})
        }
    }


    render() {
        const {id, setProps, url, msg} = this.props;

        return (
            <div id={id}>
                <table>
                    <tbody>
                        <tr><td>url</td><td><input defaultValue={url}/></td></tr>
                        <tr><td>msg</td><td><textarea defaultValue={msg}/></td></tr>
                    </tbody>
                </table>
            </div>
        );
    }
}

DashWebsocket.defaultProps = {};

DashWebsocket.propTypes = {
    /**
     * The ID used to identify this component in Dash callbacks.
     */
    id: PropTypes.string,

    /**
     * The websocket response message.
     */
    msg: PropTypes.string,

    /**
     * The url for websocket.
     */    
    url: PropTypes.string.isRequired,

    /**
     * Dash-assigned callback that should be called to report property changes
     * to Dash, to make them available for callbacks.
     */
    setProps: PropTypes.func
};
3 Likes

Really interesting. I have been thinking about making such a component for a while, but I have been kind of split between websockets and socketio. Why did you decided to go for websockets rather than socketio? :slight_smile:

EDIT: And why W3CWebSocket rather than just WebSocket?

@Emil I actually treid socketio as well, it can achieve the same result, and I am using Flask-SocketIO as the socket io server. However, I found the Flask-SocketIO is not stable and offen disconnect. Also, socketio is not using native websocket, hence it is not able to talk to other pure websocket server/client. So I made a dash websocket component. In term of why W3CWebSocket, since I read the api documents said W3C WebSocket API for applications running on both Node and browsers, so I guess it is more generic and suitable for more scenarios. I think the other React websocket controller should work as well.