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)?
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
Related
I am trying to create dashboard by using the plotly python. Require to create dropdown for date selection for the pie chart. All the data are come from a .csv file.
Expectation: The data displayed in the pie chart are based on the date selected.
Data:
enter image description here
Code:
date_category = list(df['Date'].unique())
app.layout = ...,
dcc.Dropdown(id='date_drdn', multi=False, value= ['02/01/2022'],
options = [{'label':x, 'value':x}
for x in date_category]
),
dcc.Graph(id='pie-fig', figure={})
#app.callback(
Output('pie-fig', 'figure'),
Input('date_drdn', 'value'))
def update_graph(selection):
dff = df[df['Date'].isin(selection)]
fig = px.pie(dff, values='Transactions', names='Product', color_discrete_sequence=px.colors.sequential.RdBu)
fig.update_traces(textinfo= "label+value+percent").update_layout(title_x=0.5)
return fig
However, it keep on showing the error message when select the date.
Error message:"only list-like objects are allowed to be passed to isin(), you passed a [str]"
And the data is not display based on the date selected.
Does anyone know why and how to solve it?
If the return value of the dropdown allows multiple selections, it will be in list format and isin effect. Since the expected pie chart is a single selection of date and time, the list format is not needed as an initial value. At the same time, the return value of the callback will be a single date and time data for conditional extraction.
date_category = list(df['Date'].unique())
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
#from jupyter_dash import JupyterDash
app = Dash(__name__)
#app = JupyterDash(__name__)
app.layout = html.Div([
html.H3('Daily Graph'),
dcc.Dropdown(id='date_drdn',
multi=False,
value= '02/01/2022',
options = [{'label':x, 'value':x} for x in date_category]
),
dcc.Graph(id='pie-fig', figure={})
])
#app.callback(
Output('pie-fig', 'figure'),
Input('date_drdn', 'value'))
def update_graph(selection):
# if selection:
dff = df[df['Date'] == selection]
#print(dff)
fig = px.pie(dff, values='Transactions', names='Product', color_discrete_sequence=px.colors.sequential.RdBu)
fig.update_traces(textinfo="label+value+percent").update_layout(title_x=0.5)
return fig
if __name__ == '__main__':
app.run_server(debug=True)#, mode='inline'
I'm using the Dash Leaflet map component dash-leaflet for my interactive map visualizations.
My goal is to filter large GeoJSON components (dl.GeoJSON) by values of dash components (e.g. dcc.Slider).
My current approach is the following:
import random
import dash
import dash_html_components as html
import dash_leaflet as dl
import dash_leaflet.express as dlx
import dash_core_components as dcc
from dash.dependencies import Input, Output
# Create some markers.
points = [dict(lat=55.5 + random.random(), lon=9.5 + random.random(), value=random.random()*100) for i in range(100)]
data = dlx.dicts_to_geojson(points)
app = dash.Dash()
app.layout = html.Div([
dl.Map([
dl.TileLayer(),
dl.GeoJSON(id="data-id", data=data)
], center=(56, 10), zoom=8, style={'height': '50vh'}),
html.Div([
html.H5('Filtering'),
dcc.Slider(id='my-slider', min=0, max=100, step=1, value=100),
html.Div(id='slider-output-container')
], style={'width': '30%'}),
])
#app.callback(
Output('slider-output-container', 'children'),
Output('data-id', 'data'),
[Input('my-slider', 'value')])
def update_output(value):
points_new = [p for p in points if p['value'] <= value]
data_new = dlx.dicts_to_geojson(points_new)
return 'You have selected value "{}"'.format(value), data_new
if __name__ == '__main__':
app.run_server()
In this example, the data object of the GeoJSON component "data-id" is filtered with the value of a dash component "my-slider" by filtering the list of points depending on the input value and returning a new geoJSON object created with the dlx.dicts_to_geojson function:
points_new = [p for p in points if p['value'] <= value]
data_new = dlx.dicts_to_geojson(points_new)
Is this the right way to filter a geoJSON object?
I can imagine that there exists better ways to define such a filter function on the client side with the options-feature of the GeoJSON component and a javascript function but I don't know how.
I appreciate any advice/code example I can get.
While it is possible to filter the data in Python, it can introduce significant network overhead depending on the data size (the data is transferred from the server to the client each time the filter changes). If you do the filtering client side, you only have to transfer the data once, i.e. the performance difference can be dramatic.
The client side filtering can be implemented by adding a JavaScript asset (i.e. a .js file placed in the assets folder) with the filtering function as per the documentation,
window.myNamespace = Object.assign({}, window.myNamespace, {
mySubNamespace: {
filter_features: function(feature, context) {
// code should return true if feature is included, otherwise false
const value = context.props.hideout['value']
...
}
}
});
To apply the filtering, pass a function handle to the filter function to the GeoJSON component,
import dash_leaflet as dl
from dash_extensions.javascript import Namespace
...
ns = Namespace("myNamespace ", "mySubNamespace")
dl.GeoJSON(id="geojson", options=dict(filter=ns("filter_features"), ...)
Finally, by making the filter dependent of the hideout prop (as indicated in the example code above), you can achieve interactivity by updating this prop via a callback,
#app.callback(Output("geojson", "hideout"), ...)
def update(...):
...
return {"value": value}
EDIT: As per request in the comments, here is a small self-contained example demonstrating interactive geojson filtering using only client side logic,
import dash_html_components as html
import dash_leaflet as dl
import dash_core_components as dcc
import dash_leaflet.express as dlx
from dash import Dash
from dash.dependencies import Output, Input
from dash_extensions.javascript import assign
# A few cities in Denmark.
cities = [dict(name="Aalborg", lat=57.0268172, lon=9.837735),
dict(name="Aarhus", lat=56.1780842, lon=10.1119354),
dict(name="Copenhagen", lat=55.6712474, lon=12.5237848)]
# Create drop down options.
dd_options = [dict(value=c["name"], label=c["name"]) for c in cities]
dd_defaults = [o["value"] for o in dd_options]
# Generate geojson with a maker for each city and name as tooltip.
geojson = dlx.dicts_to_geojson([{**c, **dict(tooltip=c['name'])} for c in cities])
# Create javascript function that filters on feature name.
geojson_filter = assign("function(feature, context){return context.props.hideout.includes(feature.properties.name);}")
# Create example app.
app = Dash()
app.layout = html.Div([
dl.Map(children=[
dl.TileLayer(),
dl.GeoJSON(data=geojson, options=dict(filter=geojson_filter), hideout=dd_defaults, id="geojson")
], style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block"}, id="map"),
dcc.Dropdown(id="dd", value=dd_defaults, options=dd_options, clearable=False, multi=True)
])
# Link drop down to geojson hideout prop (could also be done with a normal callback).
app.clientside_callback("function(x){return x;}", Output("geojson", "hideout"), Input("dd", "value"))
if __name__ == '__main__':
app.run_server()
Note that it requires dash-extensions==0.0.55.
I have written a basic plotly dash app that pulls in data from a csv and displays it on a chart.
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.
The fix is normally that you define your app.layout as a function, as outlined here (scroll down to updates on page load). 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
Removing the initial df = pd.read_csv(path) before the def layout_function():. This results in an error.
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.
Defining df within the update_graph callback. 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)
In short, i think that defining app.layout = layout_function should make this work, but it doesn't. What am I missing/not seeing?
Appreciate any help.
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 (e.g. to feather) and/or reduce the data size via pre processing. 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.
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.
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). 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.
I have a situation at the moment where I am stuck on making my graph to change whenever the input in textbox changes. I also wanted to make sure that whenever changes made to the textbox, it will reflect to the output I wanted from the DB [the graph] and this should be done continuously ie the graph will flow continuously.
However after some tries in using a button to kickstart the n-intervals, I still failed in doing so.
It'd will be great if anyone can have a look at my code. Thank you so much.
import dash
from dash.dependencies import Output, Input
import dash_core_components as dcc
import dash_html_components as html
import plotly
import random
import plotly.graph_objs as go
from collections import deque
import sqlite3
import pandas as pd
import time
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(
[ html.H2('Live Twitter Sentiment Trend'),
dcc.Input(id='sentiment_term', value='trump', type='text'),
dcc.Graph(id='live-graph', animate=False),
dcc.Interval(
id='graph-update',
interval=1*1000,
n_intervals = 0
),
]
)
#app.callback(
Output('live-graph', 'figure'),
[Input(component_id='sentiment_term', component_property='value'),
[Input(component_id='graph-update', component_property='n_intervals')])
def update_graph_scatter(sentiment_term):
try:
conn = sqlite3.connect('twitter.db')
conn.cursor()
df = pd.read_sql("SELECT * FROM sentiment WHERE tweet LIKE ? ORDER BY unix DESC LIMIT 1000", conn ,params=('%' + sentiment_term + '%',))
df.sort_values('unix', inplace=True)
df['sentiment_smoothed'] =
df['sentiment'].rolling(int(len(df)/2)).mean()
df['date'] = pd.to_datetime(df['unix'],unit='ms')
df.set_index('date', inplace=True)
df = df.resample('0.15min').mean()
df.dropna(inplace=True)
X = df.index
Y = df.sentiment_smoothed
data = plotly.graph_objs.Scatter(
x=X,
y=Y,
name='Scatter',
mode= 'lines+markers'
)
return {'data': [data],'layout' : go.Layout(xaxis=dict(range=[min(X),max(X)]),
yaxis=dict(range=[min(Y),max(Y)]),
title='Term: {}'.format(sentiment_term))}
except Exception as e:
with open('errors.txt','a') as f:
f.write(str(e))
f.write('\n')
if __name__ == '__main__':
app.run_server(debug=True)
Your callback function update_graph_scatter is missing a second parameter. You can change it to:
def update_graph_scatter(sentiment_term, n_intervals):
Also your callback decorator should be:
#app.callback(
Output('live-graph', 'figure'),
[Input(component_id='sentiment_term', component_property='value'),
Input(component_id='graph-update', component_property='n_intervals')])
Because it had an extra "[" breaking it.
I'm not sure if this is causing your problem, but it might. I wish I had 50 reputation to post this as a mere comment because of its minor contribution.
I am using Dash to build an application and part of the application is displaying a data table. I convert the table from a pandas dataframe to a Bootstrap Table in Dash using the Table() function from dash-bootstrap-components.
With my table set up
import dash-bootstrap-components as dbc
import dash_html_components as html
import dash
data_table = dbc.Table(# code to construct table)
I'd like to use the dbc.Tooltip to add a tooltip to items (html.td() elements) in my data_table. My first thought was to assign each element in the table an id corresponding to its position and then attach a tooltip
tooltip = dbc.Tooltip("Tooltip text", target = id)
Then we put it all together.
app = dash.Dash(_name_)
app.layout = html.Div(
children = [data_table, tooltip]
)
However, this doesn't seem to be working and I am struggling with how to implement this theoretically and cannot find much help.
UPDATE:
Here is some example code of what I am trying to do. It gives an error, if you remove the sectioned commented "REMOVE TO WORK" the code will function.
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import pandas as pd
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
data = {
"Name" : ["Tom", "Jack", "Jill", "Jane"],
"Age" : [21, 30, 45, 80]
}
fixt_df = pd.DataFrame.from_dict(data)
#Setup table
table = dbc.Table(
# Header
[html.Thead([html.Tr([html.Th(col) for col in fixt_df.columns])])] +
# Body
[html.Tbody([html.Tr([html.Td(html.A(fixt_df.iloc[i][col], id = ""+str(i)+"_" + col))
for col in fixt_df.columns])
for i in range(len(fixt_df))])]
,
hover = True,
className = "table-responsive table-hover"
)
items = [table]
# REMOVE TO WORK
for col in fixt_df.columns:
for i in range(len(fixt_df)):
items.extend([dbc.Tooltip("test", target = str(i)+"_"+col)])
# REMOVE TO WORK
app.layout = html.Div(
children = [
#header
html.Div(
html.H1("Example")
),
#table
html.Div(
items
)
]
)
if __name__ == '__main__':
app.run_server(debug=True)
I found your problem - for some reason the dbc.Tooltip element doesn't play well with elements that have IDs starting with a number.
To overcome this, in the element IDs and tooltip targets simply change:
str(i)+"_"+col
to:
col+"_"+str(i)
Alternatively, you can add a letter prefix:
"p_"+str(i)+"_"+col