Combine DateRangePicker and Dropdown in Plotly Dash - python

I'm trying to use DateRangePicker and Dropdown to callback a dash_table. Actually I tried to call dropdown option first and then call dash_table after calling options, but it didn't work.
I don't know how to add a second call back that is based on dropdown options.
Below is my code:
import pandas as pd
import numpy as np
from datetime import datetime
import plotly.express as px
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import dash_table
import dash_bootstrap_components as dbc
from datetime import datetime as dt
lst_str_cols = ['BR_CD']
dict_dtypes = {x : 'str' for x in lst_str_cols}
df = pd.read_excel('https://github.com/hoatranobita/test_datepicker/blob/main/MD6200(20150101_20201231)_2%20(1).xlsx?raw=true')
df['ISSUE_DATE_2'] = df['ISSUE_DATE']
df['ISSUE_DATE'] = pd.to_datetime(df['ISSUE_DATE'],infer_datetime_format=True,errors='coerce')
df.set_index('ISSUE_DATE',inplace=True)
app = dash.Dash(__name__,external_stylesheets=[dbc.themes.LUX])
branches = df['BR_CD'].unique().tolist()
app.layout = html.Div([
dbc.Row([
dbc.Col([html.H5('Drop Down',className='text-center'),
dcc.DatePickerRange(
id='my-date-picker-range', # ID to be used for callback
calendar_orientation='horizontal', # vertical or horizontal
day_size=39, # size of calendar image. Default is 39
end_date_placeholder_text="Return", # text that appears when no end date chosen
with_portal=False, # if True calendar will open in a full screen overlay portal
first_day_of_week=0, # Display of calendar when open (0 = Sunday)
reopen_calendar_on_clear=True,
is_RTL=False, # True or False for direction of calendar
clearable=True, # whether or not the user can clear the dropdown
number_of_months_shown=1, # number of months shown when calendar is open
min_date_allowed=dt(2015, 1, 1), # minimum date allowed on the DatePickerRange component
max_date_allowed=dt.today(), # maximum date allowed on the DatePickerRange component
initial_visible_month=dt.today(), # the month initially presented when the user opens the calendar
#end_date_placeholder_text='DD-MM-YYYY',
start_date=dt(2015, 1, 1).date(),
end_date=dt.today().date(),
display_format='DDMMYYYY', # how selected dates are displayed in the DatePickerRange component.
month_format='MMMM, YYYY', # how calendar headers are displayed when the calendar is opened.
minimum_nights=0, # minimum number of days between start and end date
persistence=True,
persisted_props=['start_date'],
persistence_type='session', # session, local, or memory. Default is 'local'
updatemode='singledate' # singledate or bothdates. Determines when callback is triggered
)],width={'size':4,"offset":0,'order':1}),
dbc.Col([html.H5('Drop Down',className='text-center'),
dcc.Dropdown(
id='filter_dropdown',placeholder="Please select branch code",
options=[{'label':br, 'value':br} for br in branches],
value = [],
multi=True,
disabled=False,
clearable=True,
searchable=True)],width={'size':2,"offset":0,'order':1}),
]),
dbc.Row([
dbc.Col([html.H5('Disbursement List',className='text-center'),
dash_table.DataTable(id='table-container',data=[],
columns=[{"name":i,"id":i,'type':'numeric'} for i in df.columns],
style_table={'overflow':'scroll','height':500},
style_cell={'textAlign':'center'})
],width={'size':12,"offset":0,'order':1})
])
])
#app.callback(Output('table-container', 'data'),
[Input('my-date-picker-range', 'start_date'),
Input('my-date-picker-range', 'end_date')])
def update_data(start_date, end_date):
data = df.loc[start_date: end_date]
return data.to_dict('rows')
if __name__ == '__main__':
app.run_server(debug=False, dev_tools_ui=False,port=8051)

You can include both filters in your callback as follows:
#app.callback(Output('table-container', 'data'),
[Input('my-date-picker-range', 'start_date'),
Input('my-date-picker-range', 'end_date'),
Input('filter_dropdown', 'value')])
def update_data(selected_start_date, selected_end_date, selected_branches):
# filter the data frame based on the DatePickerRange selection
data = df[(df.index >= selected_start_date) & (df.index <= selected_end_date)]
# filter the data frame based on the Dropdown selection
if selected_branches != []:
data = data[data['BR_CD'].isin(selected_branches)]
return data.to_dict(orient='records')

Related

Plotly: is there a way to save the data of a clicked point in a list?

I have a 2D plotly graph with a hover feature. When you hover over each point, the label (e.g. 'image 2, cluster 1') associated with that point appears. I'd like for label to be appended onto an existing list if I were to click on the point (rather than just hover over it). The reason why is that I'd later like to use the data of this point to perform another task. Is there an example online that demonstrates how to do this-- have looked through the documentation but haven't found something for this yet. Thanks!
The hoverData that is available to you by default, with some sample data, is this:
{
"points": [
{
"curveNumber": 1,
"pointNumber": 7,
"pointIndex": 7,
"x": 1987,
"y": 74.32,
"bbox": {
"x0": 420.25,
"x1": 426.25,
"y0": 256,
"y1": 262
}
}
]
}
I'm not quite sure what you mean by 'label', so I can only assume that it would be the name of a trace or something similar, like in this example from the Plotly docs:
But as you can see, that's not readily available in the hoverData dict. This means that you'll have to use this information to reference your figure structure as well, so that you end up with something like this:
[['New Zealand', 2002, 79.11]]
And that's not a problem as long as you're willing to use Plotly Dash. I've made a complete setup for you that should meet your requirements. In the app in the image below you'll find a figure along with two output fields for strings. The first field shows the info from that last point you've clicked in the figure. On every click, a new element is added to a list named store. The last fields shows the complete information from the same click.
The answer to your question is, yes, there is a way to save the data of a clicked point in a list. And one way to do so is through the following callback that uses clickdata to reference your figure object, store those references in a list, and append new elements every time you click a new element.
App
Complete code:
import json
from textwrap import dedent as d
import pandas as pd
import plotly.graph_objects as go
import numpy as np
import dash
from dash import dcc
import dash_html_components as html
import plotly.express as px
from dash.dependencies import Input, Output
from jupyter_dash import JupyterDash
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
# app info
app = JupyterDash(__name__)
styles = {
'pre': {
'border': 'thin lightgrey solid',
'overflowX': 'scroll'
}
}
# data
df = px.data.gapminder().query("continent=='Oceania'")
# plotly figure
fig = px.line(df, x="year", y="lifeExp", color="country", title="No label selected")
fig.update_traces(mode="markers+lines")
app.layout = html.Div([
dcc.Graph(
id='figure1',
figure=fig,
),
html.Div(className
='row', children=[
html.Div([
dcc.Markdown(d("""Hoverdata using figure references""")),
html.Pre(id='hoverdata2', style=styles['pre']),
], className='three columns'),
html.Div([
dcc.Markdown(d("""
Full hoverdata
""")),
html.Pre(id='hoverdata1', style=styles['pre']),
], className='three columns')
]),
])
# container for clicked points in callbacks
store = []
#app.callback(
Output('figure1', 'figure'),
Output('hoverdata1', 'children'),
Output('hoverdata2', 'children'),
[Input('figure1', 'clickData')])
def display_hover_data(hoverData):
if hoverData is not None:
traceref = hoverData['points'][0]['curveNumber']
pointref = hoverData['points'][0]['pointNumber']
store.append([fig.data[traceref]['name'],
fig.data[traceref]['x'][pointref],
fig.data[traceref]['y'][pointref]])
fig.update_layout(title = 'Last label was ' + fig.data[traceref]['name'])
return fig, json.dumps(hoverData, indent=2), str(store)
else:
return fig, 'None selected', 'None selected'
app.run_server(mode='external', port = 7077, dev_tools_ui=True,
dev_tools_hot_reload =True, threaded=True)
You need to use callbacks to perform this type of action (register on_click()). Have defined clicked as a list of clicked points. Demonstrated how this can be achieved with ipwidgets or dash
ipwidgets
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import ipywidgets as widgets
from pathlib import Path
import json
x = np.random.uniform(-10, 10, size=50)
y = np.sin(x)
clicked = []
# construct figure that has holders for points, interpolated line and final lines
fig = go.FigureWidget(
[
go.Scatter(x=x, y=y, mode="markers", name="base_points"),
]
)
fig.update_layout(template="simple_white")
out = widgets.Output(layout={"border": "1px solid black"})
out.append_stdout("Output appended with append_stdout\n")
# create our callback function
#out.capture()
def base_click(trace, points, selector):
global clicked
clicked.append(points.__dict__)
fig.data[0].on_click(base_click)
widgets.HBox([fig, out])
dash
from jupyter_dash import JupyterDash
import dash
from dash.dependencies import Input, Output, State
import numpy as np
import json
clicked = []
# Build App
app = JupyterDash(__name__)
app.layout = dash.html.Div(
[
dash.dcc.Graph(
id="fig",
figure=go.Figure(go.Scatter(x=x, y=y, mode="markers", name="base_points")),
),
dash.html.Div(id="debug"),
]
)
#app.callback(
Output("debug", "children"),
Input("fig", "clickData"),
)
def point_clicked(clickData):
global clicked
clicked.append(clickData)
return json.dumps(clickData)
# Run app and display result inline in the notebook
app.run_server(mode="inline")

dash drawing different graph when clicking on different button

I want to draw graph A when clicking on button A and graph B when clicking on button B, if either graphs is drawn and I click on either button I want the page to be updated with the new graph, I'm using dash and plotly, here the pseudo code
#app.callback([Output("graph-content", "children")],[Input("KPI_gauge","n_clicks")])
def kpi_gauge(v):
if v == None:
raise PreventUpdate
fig1 = KPIGauge(88.7,102)
return [dcc.Graph(figure=fig1)]
#app.callback([Output("graph-content", "children")],[Input("comparaison_graph","n_clicks")]
def comparaison_graph(v):
if v == None:
raise PreventUpdate
fig = ComparaisonGraph()
return [dcc.Graph(figure=fig)]
but I get this error dash duplicate callback output, how to resolve this error?
https://dash.plotly.com/callback-gotchas A component/property pair can only be the Output of one callback. Your code broke this rule, graph-content was the output of two callbacks
this code demonstrates a way to output plotly figure on first click and update data in graph on subsequent clicks
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import dash
from jupyter_dash import JupyterDash
import random
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
import numpy as np
# Build App
app = JupyterDash(__name__)
app.layout = html.Div(
[
html.Button("KPI", id="KPI_gauge", n_clicks=0),
html.Button("Graph", id="comparaison_graph", n_clicks=0),
html.Div(id="graph-content"),
]
)
#app.callback(
Output("graph-content", "children"),
Input("KPI_gauge", "n_clicks"),
Input("comparaison_graph", "n_clicks"),
State("graph-content", "children"),
)
def update_graph(kpi_but, graph_but, content):
if not kpi_but and not graph_but:
raise PreventUpdate
ctx = dash.callback_context
button = ctx.triggered[0]["prop_id"].split(".")[0]
fig_id = f"{button}_fig"
if button=="KPI_gauge":
if content and content["type"]=="Graph" and content["props"]["id"]==fig_id: # update
fig = content["props"]["figure"]
fig["data"][0]["value"] = random.randint(0,400)
else: # create
fig = go.Figure(go.Indicator(mode="gauge+number",value=270,title={"text": "KPI"},))
elif button=="comparaison_graph":
if content and content["type"]=="Graph" and content["props"]["id"]==fig_id: # update
fig = content["props"]["figure"]
fig["data"][0]["y"] = np.sort(np.random.uniform(1,5,365*2))
else: # create
df = pd.DataFrame({"date":pd.date_range("1-aug-2019", periods=365*2), "value":np.linspace(1,5,365*2)})
fig = px.line(df, x="date", y="value")
else:
raise PreventUpdate
return dcc.Graph(id=fig_id, figure=fig)
app.run_server(mode="inline", port=8051)

Dashboard with DASH - Argument issues dcc.graph()

I'm trying to create a dashboard in Pycharm using dash. Here is the error I keep receiving,
html.Div(dcc.Graph(id='line-plot')),
TypeError: Graph() takes no arguments
And below is a snippet of my code where the error is being found (bottom of code). This code ran fine and I was about to populate the dashboard without receiving any errors inside IBM's python environment. I'm assuming I have to tweak something
# TASK 3 - UPDATE LAYOUT COMPONENETS
# html.H1 tag for title , style, and overall font size
# html.Div & dcc.Input() tag to set inputs of the dashboard
# Update output componenent 2nd html.Div to layout the graph dcc.Graph()
app.layout = html.Div(children=[html.H1('Airline Performance Dashboard',
style={'textAlign': 'center', 'color': '#503D36',
'font-size': 40}),
html.Div(["Input Year: ", dcc.Input(id='input-year', value='2010',
type='number',
style={'height': '50px', 'font-size': 35}), ],
style={'font-size': 40}),
html.Br(),
html.Br(),
html.Div(dcc.Graph(id='line-plot')),
])
Here is the rest of the code,
# TASK 4 - ADD APPLICATION CALL BACK FUNCTION and outputs / inputs
# add callback decorator
#app.callback(Output(component_id='line-plot', component_property='figure'),
Input(component_id='input-year', component_property='value'))
# Add computation to callback function and return graph
def get_graph(entered_year):
# Select 2019 data
df = airline_data[airline_data['Year'] == int(entered_year)]
# Group the data by Month and compute average over arrival delay time.
line_data = df.groupby('Month')['ArrDelay'].mean().reset_index()
# TASK 5 - UPDATE CALL BACK FUNCTION go.Figure(data=) and update fig.update_layout()
fig = go.Figure(
data=go.Scatter(x=line_data['Month'], y=line_data['ArrDelay'], mode='lines', marker=dict(color='green')))
fig.update_layout(title='Month vs Average Flight Delay Time', xaxis_title='Month', yaxis_title='ArrDelay')
return fig
# Run the app
if __name__ == '__main__':
app.run_server()
Its safe to say I need an adult.
have added import and simulation of data frame
all other code runs without issue on plotly 5.1.0
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import numpy as np
app = JupyterDash(__name__)
# simulate data...
dr = pd.date_range("1-jan-2010", freq="W", periods=200)
airline_data = pd.DataFrame({"Year":dr.year, "Month":dr.month, "ArrDelay":np.random.uniform(2,5,len(dr))})
# TASK 3 - UPDATE LAYOUT COMPONENETS
# html.H1 tag for title , style, and overall font size
# html.Div & dcc.Input() tag to set inputs of the dashboard
# Update output componenent 2nd html.Div to layout the graph dcc.Graph()
app.layout = html.Div(children=[html.H1('Airline Performance Dashboard',
style={'textAlign': 'center', 'color': '#503D36',
'font-size': 40}),
html.Div(["Input Year: ", dcc.Input(id='input-year', value='2010',
type='number',
style={'height': '50px', 'font-size': 35}), ],
style={'font-size': 40}),
html.Br(),
html.Br(),
html.Div(dcc.Graph(id='line-plot')),
])
# TASK 4 - ADD APPLICATION CALL BACK FUNCTION and outputs / inputs
# add callback decorator
#app.callback(Output(component_id='line-plot', component_property='figure'),
Input(component_id='input-year', component_property='value'))
# Add computation to callback function and return graph
def get_graph(entered_year):
# Select 2019 data
df = airline_data[airline_data['Year'] == int(entered_year)]
# Group the data by Month and compute average over arrival delay time.
line_data = df.groupby('Month')['ArrDelay'].mean().reset_index()
# TASK 5 - UPDATE CALL BACK FUNCTION go.Figure(data=) and update fig.update_layout()
fig = go.Figure(
data=go.Scatter(x=line_data['Month'], y=line_data['ArrDelay'], mode='lines', marker=dict(color='green')))
fig.update_layout(title='Month vs Average Flight Delay Time', xaxis_title='Month', yaxis_title='ArrDelay')
return fig
app.run_server(mode="inline")

Faster serializations (pickle, parquet, feather, ...) than json in plotly dash Store?

Context
In a dashboard using plotly Dash I need to perform an expensive download from DB only when a component (DataPicker with the period to consider and so to be downloaded from DB) is updated and then use the resulting DataFrame with other components (e.g. Dropdowns filtering the DataFrame) avoiding the expensive download process.
The docs suggests to use dash_core_components.Store as Output of a callback that return the DataFrame serielized in json and than use the Store as Input of other callbacks that needs to deserialize from json to DataFrame.
Serialization from/to JSON is slow, and each time I update a component it takes 30 seconds to update the plot just for that.
I tried to use faster serializations like pickle, parquet and feather but in the deserialization part I get an error stating that the object is empty (when using JSON no such error appear).
Question
Is it possible to perform serializations in Dash Store with faster methods like pickle, feather or parquet (they takes approx half of time for my dataset) than JSON? How?
Code
import io
import traceback
import pandas as pd
from datetime import datetime, date, timedelta
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
from plotly.subplots import make_subplots
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
today = date.today()
app.layout = html.Div([
dbc.Row(dbc.Col(html.H1('PMC'))),
dbc.Row(dbc.Col(html.H5('analysis'))),
html.Hr(),
html.Br(),
dbc.Container([
dbc.Row([
dbc.Col(
dcc.DatePickerRange(
id='date_ranges',
start_date=today - timedelta(days=20),
end_date=today,
max_date_allowed=today, display_format='MMM Do, YY',
),
width=4
),
]),
dbc.Row(
dbc.Col(
dcc.Dropdown(
id='dd_ycolnames',
options=options,
value=default_options,
multi=True,
),
),
),
]),
dbc.Row([
dbc.Col(
dcc.Graph(
id='graph_subplots',
figure={},
),
width=12
),
]),
dcc.Store(id='store')
])
#app.callback(
Output('store', 'data'),
[
Input(component_id='date_ranges', component_property='start_date'),
Input(component_id='date_ranges', component_property='end_date')
]
)
def load_dataset(date_ranges_start, date_ranges_end):
# some expensive clean data step
logger.info('loading dataset...')
date_ranges1_start = datetime.strptime(date_ranges_start, '%Y-%m-%d')
date_ranges1_end = datetime.strptime(date_ranges_end, '%Y-%m-%d')
df = expensive_load_from_db(date_ranges1_start, date_ranges1_end)
logger.info('dataset to json...')
#return df.to_json(date_format='iso', orient='split')
return df.to_parquet() # <----------------------
#app.callback(
Output(component_id='graph_subplots', component_property='figure'),
[
Input(component_id='store', component_property='data'),
Input(component_id='dd_ycolnames', component_property='value'),
],
)
def update_plot(df_bin, y_colnames):
logger.info('dataset from json')
#df = pd.read_json(df_bin, orient='split')
df = pd.read_parquet(io.BytesIO(df_bin)) # <----------------------
logger.info('building plot...')
traces = []
for y_colname in y_colnames:
if df[y_colname].dtype == 'bool':
df[y_colname] = df[y_colname].astype('int')
traces.append(
{'x': df['date'], 'y': df[y_colname].values, 'name': y_colname},
)
fig = make_subplots(
rows=len(y_colnames), cols=1, shared_xaxes=True, vertical_spacing=0.1
)
fig.layout.height = 1000
for i, trace in enumerate(traces):
fig.append_trace(trace, i+1, 1)
logger.info('plotted')
return fig
if __name__ == '__main__':
app.run_server(host='localhost', debug=True)
Error text
OSError: Could not open parquet input source '<Buffer>': Invalid: Parquet file size is 0 bytes
Due to the exchange of data between client and server, you are currently limited to JSON serialization. One way to circumvent this limitation is via the ServersideOutput component from dash-extensions, which stores the data on the server. It uses file storage and pickle serialization by default, but you can use other storage (e.g. Redis) and/or serialization protocols (e.g. arrow) as well. Here is a small example,
import time
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
from dash_extensions.enrich import Dash, Output, Input, State, ServersideOutput
app = Dash(prevent_initial_callbacks=True)
app.layout = html.Div([
html.Button("Query data", id="btn"), dcc.Dropdown(id="dd"), dcc.Graph(id="graph"),
dcc.Loading(dcc.Store(id='store'), fullscreen=True, type="dot")
])
#app.callback(ServersideOutput("store", "data"), Input("btn", "n_clicks"))
def query_data(n_clicks):
time.sleep(1)
return px.data.gapminder() # no JSON serialization here
#app.callback(Input("store", "data"), Output("dd", "options"))
def update_dd(df):
return [{"label": column, "value": column} for column in df["year"]] # no JSON de-serialization here
#app.callback(Output("graph", "figure"), [Input("dd", "value"), State("store", "data")])
def update_graph(value, df):
df = df.query("year == {}".format(value)) # no JSON de-serialization here
return px.sunburst(df, path=['continent', 'country'], values='pop', color='lifeExp', hover_data=['iso_alpha'])
if __name__ == '__main__':
app.run_server()

Null is not an object (evaluating ‘n.layout’) in Dash Plotly

I am developing dashboard using Dash Plotly and I am getting an error when I click tabs.
The error says null is not an object (evaluating ‘n.layout’)
(This error originated from the built-in JavaScript code that runs Dash apps. Click to see the full stack trace or open your browser’s console.)"
Can any one help me to solve this problem?
My code is found below.
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_daq as daq
from dash.dependencies import Input, Output
import plotly.graph_objs as go
import pandas as pd
import numpy as np
from copy import copy
import dash_table
import json
import base64
import plotly.express as px
#Data
errors = pd.read_csv(r’/Users/kapital/Documents/ABCD/PM/errors.csv’)
external_stylesheets = [‘https://codepen.io/chriddyp/pen/bWLwgP.css’]
app = dash.Dash(name, external_stylesheets=external_stylesheets)
first_graph = dcc.Graph(id=‘graph1’,style={‘borderBottom’: ‘thin lightgrey solid’,‘padding’: ‘10px 5px’})
#, animate = True
content_tab_1 = html.Div(children = [
html.Div(first_graph, style = {‘vertical-align’:‘center’, ‘horizontal-align’:‘center’})
],
style={‘width’: ‘87%’})
app.layout = html.Div([
dcc.Tabs(id='tabs-example', value='tab-1', children=[
dcc.Tab(label='Tab one', value='tab-1',
children =[content_tab_1]),
dcc.Tab(label='Tab two', value='tab-2'),
]),
html.Div(id='tabs-example-content')
])
#app.callback(Output(‘graph1’, ‘figure’),
Input(‘tabs-example’, ‘value’))
def render_content(tab):
if tab == ‘tab-1’:
err_count = pd.DataFrame(errors['errorID'].value_counts().reset_index().values, columns=["Error", "Count"])
err_count = err_count.sort_index(axis = 0, ascending=True)
fig = px.bar(err_count, x = 'Error', y = 'Count')
return fig
# return html.Div([
# html.H3('Tab content 1')
# ])
if name == ‘main’:
app.run_server(debug=True)
The problem is that your callback function does not have an else to its conditional statement. When the user selects the second tab, that callback has no alternate path, and returns None, which gives you the error you see. I did this to fix it, but you'll probably want something more:
#app.callback(Output('graph1', 'figure'),
Input('tabs-example', 'value'))
def render_content(tab):
if tab == 'tab-1':
err_count = pd.DataFrame.from_dict(dict(
Error=[1, 2, 3, ],
Count=[5, 6, 7])
)
err_count = err_count.sort_index(axis=0, ascending=True)
fig = px.bar(err_count, x='Error', y='Count')
return fig
else:
return {}

Categories