Обновление Python Dash в реальном времени с помощью dcc.Интервал не работает

#python #websocket #plotly-dash

Вопрос:

Я пытаюсь обновить пунктирную диаграмму, открытую в моем веб — браузере, данными, поступающими с сервера websocket, который я создал. У меня нет проблем с частью websocket. Мой сервер отправляет клиенту данные, которые я добавляю в список, и преобразую этот список в объект фрейма данных pandas. Затем этот объект добавляется в мультипроцесс.Очередь.

В моем обратном вызове Dash от dcc.Интервал, я получаю данные из предыдущей очереди (эта часть, похоже, работает, потому что я могу правильно печатать данные) и обновляю свой график.

Но график в моем браузере не обновляется. Я не вижу никаких ошибок, возникающих в моем коде python или в консоли браузера. (Я пробовал с Firefox, Chrome и Brave) Моя консоль python печатает dash-update-component HTTP/1.1" 200 - мои данные при каждом интервале обратного вызова, и название вкладки Updating... хуже, иногда я вижу, что правильно отображается одно или 10 данных, а затем ничего без изменения кода, просто перезапустив свой код…

Здесь код (на стороне клиента) :

 import multiprocess as mp
import threading
import dash
from dash.dependencies import Output, Input
from dash import dcc
from dash import html
import plotly.graph_objs as go
from socket import *
import time
import json
import pandas as pd
from datetime import datetime


FLAG_QUIT = False  
FLAG_ASK_DATA = False

MAX_SIZE = 10

queue = mp.Queue()
datas = []


app = dash.Dash(__name__)
app.layout = html.Div(
    [
        html.H4('Live Chart'),
        dcc.Graph(id='live-update-graph', animate =True), 
        dcc.Interval(
            id='interval-component',
            interval=200,
            n_intervals=0
        )
    ]
)

@app.callback(Output('live-update-graph', 'figure'),
              [Input('interval-component', 'n_intervals')])
def update_graph(n):
    df = queue.get()
    print(df)
    fig = go.Scatter(x=df.index, y = df['Data'])
    return fig

    

# function for receiving message from client
def send_to_server(clsock):
    global FLAG_QUIT
    global FLAG_ASK_DATA
    while True:
        try:
            if FLAG_QUIT == True:
                break
        
            if FLAG_ASK_DATA:
                time.sleep(0.5)
                clsock.sendall("ok".encode())
        except Exception as ex:
            print(str(ex))
        

# function for receiving message from server
def recv_from_server(clsock):
    global FLAG_QUIT
    global FLAG_ASK_DATA
    while True:
        try:
            data = clsock.recv(1024).decode()
            if data == 'q':
                print('Closing connection')
                FLAG_QUIT = True
                break
            process_data(data)
            FLAG_ASK_DATA = True
        except Exception as ex:
            print(str(ex))
        

def process_data(data):
    global datas
    global queue
    process_websocket_data(data)
    datas_dataframe = convert_data_to_dataframe(datas)
    queue.put(datas_dataframe)

def convert_data_to_dataframe(data):
    data_frame = pd.DataFrame(data)
    data_frame = data_frame.drop_duplicates(0)
    data_frame_date = data_frame[0]

    final_date = []

    for time in data_frame_date.unique():
        readable = datetime.strptime(time, '%Y-%m-%d-%H-%M')
        final_date.append(readable)

    data_frame.pop(0)
    dataframe_final_date = pd.DataFrame(final_date)

    dataframe_final_date.columns = ['Date']

    final_dataframe = data_frame.join(dataframe_final_date)

    final_dataframe.set_index('Date', inplace = True)

    final_dataframe.columns = ['Data']
    return final_dataframe

def process_websocket_data(raw_data):
    data_json = json.loads(raw_data)
    data = [data_json['Date'],data_json['Data']]
    datas.append(data)
    while len(datas) > MAX_SIZE:
        datas.pop(0)


def main():
    threads = []
    HOST = 'localhost'
    PORT = 8765
    clientSocket = socket(AF_INET, SOCK_STREAM)
    connected = False
    while not connected:
        try:
            print("Waiting connecting server...")
            time.sleep(1)
            clientSocket.connect((HOST, PORT))
            connected = True
        except :
            pass
    
    print('Client is connected to a chat sever!n')
    clientSocket.send('start'.encode())

    t_send = threading.Thread(target=send_to_server, args=(clientSocket,))
    t_rcv = threading.Thread(target=recv_from_server, args=(clientSocket,))


    t_send.start()
    t_rcv.start()



if __name__ == '__main__':
    main()
    app.run_server()
 

Что я сделал неправильно или пропустил?

Изменить : Это данные, которые я получаю из очереди в методе обратного вызова update_graph(n).

                 Data
Date
2019-09-16  10302.00
2019-09-23   8061.98
2019-09-30   8042.08
2019-10-07   7851.01
2019-10-14   8274.33
2019-10-21   8218.23
2019-10-28   9534.37
2019-11-04   9197.86
2019-11-11   9041.31
2019-11-18   8504.13
 

Я также попытался заменить свою ось X списком int вместо списка дат, но результат тот же. Не освежает.

Заранее спасибо.

Комментарии:

1. Держу пари, это потому, что твой интервал слишком быстрый. 200 мс, вероятно, недостаточно для завершения сетевого запроса и обратного вызова до того, как сработает следующий интервал, который перезапустит обратный вызов. Попробуйте установить его на что-то вроде 20000 (20 секунд) для запуска и посмотрите, работает ли он, затем тщательно настройте его, чтобы увидеть, как быстро вы сможете надежно запустить его.

2. @coralvanda Спасибо вам за ваш ответ. К сожалению, я попытался с большим интервалом, как вы предлагали, но результат тот же : в моем браузере нет обновления. Возможно, то, как я пытался реализовать свое приложение, совершенно неправильно. Я не знаком с Дэшем.

3. Если вы отладите или добавите некоторые print инструкции, сможете ли вы увидеть, как выглядит ваш фрейм данных во время обратного вызова? Это может помочь направить решение.

4. @coralvanda Я отредактировал свой пост с данными, которые я получаю при обратном вызове, и провел тест, но он не сработал.

5. Хорошо, значит, у вас есть данные, это хорошо. Проблема может быть в том, как вы настраиваете точечную диаграмму. Вы могли бы попробовать жестко запрограммировать тестовый df для игры. Кроме того, вы могли бы попробовать настроить figure={} макет для dcc.Graph , потому что иногда отсутствие определенного реквизита действует забавно.

Ответ №1:

Мне наконец удалось найти проблему, и, похоже, это очередь в обратном вызове. Поэтому я удалил механизм очереди и вместо этого использовал классический список с блокировкой.

 app = dash.Dash(__name__)
app.layout = html.Div([
    dcc.Graph(id="live_graph", animate=False),
    dcc.Interval(id='interval_component',
                interval=INTERVAL,
               
    )])


@app.callback(Output('live_graph', 'figure'),
    [Input('interval_component', 'n_intervals')])
def update_graph(num):
    global datas_dataframe
    
    lock.acquire()
    if len(datas_dataframe) == 0:
        print("No data")
        lock.release()
        return no_update

    try:
        df = datas_dataframe.pop(0)
        fig = go.Figure(data=[go.Scatter(x=df.index,y=df['Data'])],
                        layout=go.Layout(yaxis=dict(tickfont=dict(size=22))))

        lock.release()
        return fig
    except Exception as ex:
        print(str(ex))
        lock.release()
        return no_update