简体   繁体   English

Python Dash 刷新页面未更新源数据

[英]Python Dash refresh page not updating source data

I have written a basic plotly dash app that pulls in data from a csv and displays it on a chart.我编写了一个基本的 plotly dash 应用程序,它从 csv 中提取数据并将其显示在图表上。 You can then toggle values on the app and the graph updates.然后,您可以在应用程序上切换值并更新图表。

However, when I add new data to the csv (done once each day) the app doesn't update the data on refreshing the page.但是,当我向 csv 添加新数据(每天完成一次)时,应用程序不会在刷新页面时更新数据。

The fix is normally that you define your app.layout as a function, as outlined here (scroll down to updates on page load).解决方法通常是将app.layout定义为 function,如此所述(向下滚动以在页面加载时更新)。 You'll see in my code below that I've done that.您会在下面的代码中看到我已经做到了。

Here's my code:这是我的代码:

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

import pandas as pd

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

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

path = 'https://raw.githubusercontent.com/tbuckworth/Public/master/CSVTest.csv'

df = pd.read_csv(path)
df2 = df[(df.Map==df.Map)]


def layout_function():

    df = pd.read_csv(path)
    df2 = df[(df.Map==df.Map)]
    
    available_strats = np.append('ALL',pd.unique(df2.Map.sort_values()))
    classes1 = pd.unique(df2["class"].sort_values())
    metrics1 = pd.unique(df2.metric.sort_values())
    
    return html.Div([
            html.Div([
                dcc.Dropdown(
                    id="Strategy",
                    options=[{"label":i,"value":i} for i in available_strats],
                    value=list(available_strats[0:1]),
                    multi=True
                ),
                dcc.Dropdown(
                    id="Class1",
                    options=[{"label":i,"value":i} for i in classes1],
                    value=classes1[0]
                ),
                dcc.Dropdown(
                    id="Metric",
                    options=[{"label":i,"value":i} for i in metrics1],
                    value=metrics1[0]
                )],
            style={"width":"20%","display":"block"}),
                
        html.Hr(),
    
        dcc.Graph(id='Risk-Report')          
    ])
            
app.layout = layout_function


@app.callback(
        Output("Risk-Report","figure"),
        [Input("Strategy","value"),
         Input("Class1","value"),
         Input("Metric","value"),
         ])

def update_graph(selected_strat,selected_class,selected_metric):
    if 'ALL' in selected_strat:
        df3 = df2[(df2["class"]==selected_class)&(df2.metric==selected_metric)]
    else:
        df3 = df2[(df2.Map.isin(selected_strat))&(df2["class"]==selected_class)&(df2.metric==selected_metric)]
    df4 = df3.pivot_table(index=["Fund","Date","metric","class"],values="value",aggfunc="sum").reset_index()
    traces = []
    for i in df4.Fund.unique():
        df_by_fund = df4[df4["Fund"] == i]
        traces.append(dict(
                x=df_by_fund["Date"],
                y=df_by_fund["value"],
                mode="lines",
                name=i
                ))
    
    if selected_class=='USD':
        tick_format=None
    else:
        tick_format='.2%'
    
    return {
            'data': traces,
            'layout': dict(
                xaxis={'type': 'date', 'title': 'Date'},
                yaxis={'title': 'Values','tickformat':tick_format},
                margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
                legend={'x': 0, 'y': 1},
                hovermode='closest'
            )
        }
    

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

Things I've tried我试过的事情

  1. Removing the initial df = pd.read_csv(path) before the def layout_function(): .def layout_function():之前删除初始df = pd.read_csv(path) This results in an error.这会导致错误。
  2. Creating a callback button to refresh the data using this code:使用此代码创建回调按钮以刷新数据:
@app.callback(
        Output('Output-1','children'),
        [Input('reload_button','n_clicks')]        
        )

def update_data(nclicks):
    if nclicks == 0:
        raise PreventUpdate
    else:
        df = pd.read_csv(path)
        df2 = df[(df.Map==df.Map)]
        return('Data refreshed. Click to refresh again')

This doesn't produce an error, but the button doesn't refresh the data either.这不会产生错误,但按钮也不会刷新数据。

  1. Defining df within the update_graph callback.update_graph回调中定义df This updates the data every time you toggle something, which is not practicable (my real data is > 10^6 rows, so i don't want to read it in every time the user changes a toggle value)这会在您每次切换某些内容时更新数据,这是不切实际的(我的真实数据是 > 10^6 行,所以我不想每次用户更改切换值时都读取它)

In short, i think that defining app.layout = layout_function should make this work, but it doesn't.简而言之,我认为定义app.layout = layout_function应该可以完成这项工作,但事实并非如此。 What am I missing/not seeing?我错过/没有看到什么?

Appreciate any help.感谢任何帮助。

TLDR; TLDR; I would suggest that you simply load the data from within the callback.我建议您只需从回调中加载数据。 If load time is too long, you could change the format (eg to feather ) and/or reduce the data size via pre processing.如果加载时间太长,您可以更改格式(例如更改为feather )和/或通过预处理减小数据大小。 If this is still not fast enough, the next step would be to store the data in a server-side in-memory cache such as Redis .如果这仍然不够快,下一步是将数据存储在服务器端内存缓存中,例如Redis


Since you are reassigning df and df2 in the layout_function , these variables are considered local in Python , and you are thus not modifying the df and df2 variables from the global scope. While you could achieve this behavior using the global keyword , the use of global variables is discouraged in Dash .由于您在 layout_function 中重新分配dfdf2 ,这些变量在layout_function中被视为局部变量,因此您不会修改全局 scope 中的dfdf2变量。虽然您可以使用global 关键字实现此行为,但使用 global在 Dash 中不鼓励使用变量

The standard approach in Dash would be to load the data in a callback (or in the the layout_function ) and store it in a Store object (or equivalently, a hidden Div ). Dash 中的标准方法是在回调中(或在layout_function中)加载数据并将其存储在Store object(或等效地,隐藏的Div中)。 The structure would be something like结构类似于

import pandas as pd
import dash_core_components as dcc
from dash.dependencies import Output, Input

app.layout = html.Div([
    ...
    dcc.Store(id="store"), html.Div(id="trigger")
])

@app.callback(Output('store','data'), [Input('trigger','children')], prevent_initial_call=False)
def update_data(children):
    df = pd.read_csv(path)
    return df.to_json()

@app.callback(Output("Risk-Report","figure"), [Input(...)], [State('store', 'data')])
def update_graph(..., data):
    if data is None:
        raise PreventUpdate
    df = pd.read_json(data)
    ...

However, this approach will typically be much slower than just reading the data from disk inside the callback (which seems to be what you are trying to avoid) as it results in the data being transferred between the server and client.但是,这种方法通常比仅在回调中从磁盘读取数据(这似乎是您试图避免的)得多,因为它会导致数据在服务器和客户端之间传输。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM