1
votes

Below is a simple script which retrieves live population data. It updates periodically and updates the plotly figure:

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc


# Retrieve data
link = requests.get("https://countrymeters.info/en/World").text
table = pd.read_html(link)[0]
table = table
figure = table.iplot(asFigure=True, kind='bar',xTitle='Source: World',yTitle='Live Pop',title='Population')

# Dash app
app = dash.Dash(external_stylesheets=[dbc.themes.LUX])

# Bootstrap CSS
app.css.append_css({"external_url": "https://codepen.io/amyoshino/pen/jzXypZ.css"})
link = session.get("https://countrymeters.info/en/World").text

app.layout = html.Div(children=[
    html.H1("Population Data Scrape"),

    html.Div(children=
    '''
    A summary of the latest population data across the globe.
    '''     ),

    # Graph 1
    html.Div([
        dcc.Tabs([ #Tab 1
            dcc.Tab(label="Population Data", children=[

                html.Div([
                    dcc.Graph(
                        id = "Data",
                        figure = table.iplot(asFigure=True, kind='bar',xTitle='Source: World',yTitle='Live Pop',title='Population')
                            ),
                            dcc.Interval(
                                id="4secinterval",
                                interval="4000",
                                n_intervals=0
                            )], className = "six columns"),



                        ]),


                ])
            ])
        ])


# Update graph
@app.callback(Output("Data", "figure"),
            [Input("4secinterval", "n_intervals")])
def draw_figure(n):      
    test = session.get("https://countrymeters.info/en/World").text
    table = pd.read_html(test)[0]
    table = table
    figure = table.iplot(asFigure=True, kind='bar',xTitle='Source: World',yTitle='Live Pop',title='Population')
    return figure

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

In the "update graph" section of my code, for the graph to update I have to call the web scrape again to retrieve the latest data and define it in a whole function. I've tried defining the function before and using:

@app.callback(Output("Data", "figure"),
            [Input("4secinterval", "n_intervals")])
draw_figure(n)

which I was hoping would just return the figure, however, this doesn't work. Is there a way in plotly/Dash to update the figure in a shorter way (i.e without having to scrape and format the data all over again)?

1
I'm a little confused, so you want to replace the current draw_figure callback with a new one that doesn't have to scrape? Also, I don't think you can decorate a function call as a callback, it needs to be a function definition.ncasale
Yes exactly, as I have some other scrapes which take quite a lot of lines to display the plotly figure, so having it twice in the code would be quite cumbersome. But I guess you've answered my question if it's not possible to use a function call in the callback, and only a definition. It's a bit of a shame because the code will be much longer now...Nathan Thomas
It remains unclear to me what you are actually seeking. Is it just a matter of simplifying the code, i.e. not writing the same scraping code multiple times (as indicated in your comment)? Or do you intend to update the figure without scraping the data again (as indicated in your question)? I don't see how the latter should be possible; unless you do the scraping again, you don't have any new data to put in the figure?emher
So I want to update the figure by scraping new data. Since the scrape and plotting of the figure take several lines (adding dropdown boxes etc takes up a lot of code), I was hoping to define both the scrape and the plotly figure earlier in my code as a function. So later in my code I could scrape and plot the new data with just one line. But ncasale's answer tells me that it isn't possible to do this with just one line. Effectively just trying to write more elegant code. @emherNathan Thomas
That, you can do. Just write a function for scraping and plotting and call it from within the callback.emher

1 Answers

1
votes

The catch here is in the dcc.Graph section. You are calling a global variable table.iplot() where table is defined as a global variable in your retrieve section.

Try to put all the functions in a separate file say `useful_functions.py'

def draw_graph():
  link = requests.get("https://countrymeters.info/en/World").text
  table = pd.read_html(link)[0]
  table = table
  figure = table.iplot(asFigure=True, kind='bar',xTitle='Source: World',yTitle='Live Pop',title='Population')
  return figure
the_graph = draw_graph()

Now, in your main file as above, remove the global declaration of table and figure. To display the graph, in your graph section call the draw_graph() function as:

 import useful_functions as uf
 <rest of the html app code>
 dcc.Graph(
   id = "graph_id",
   figure = uf.the_graph
),

This will call the graph for the first time on load. Now for the refresh bit, the callback would look like:

@app.callback(Output("Data", "figure"),
        [Input("4secinterval", "n_intervals")])
def draw_figure(n):
    fig = uf.draw_graph()
    return fig