The graph in the app.layout is not updated at callback. Code is shown below, however, it requires a csv file of data that is turned into a pandas.Dataframe. I will post the first 26 rows here for reference. (This is a subset of results of the Houston Marathon that took place on the Saturday of the 15th of Jan!)
The code takes datetime.time data, bins it into 5 minutes of length, before creating a histogram in px.bar. Given a user's time input - their percentile in the sample distribution is visualized.
place,name,time
1,Dominic Ondoro,02:10:36
2,Tsedat Ayana,02:10:37
3,Teshome Mekonen,02:11:05
4,Parker Stinson,02:12:11
5,Tyler Pennel,02:12:16
6,Kenta Uchida,02:14:13
7,James Ngandu,02:14:28
8,Alvaro Abreu,02:14:28
9,Kevin Salvano,02:16:39
10,Tyler Pence,02:16:44
11,Phillip Reid,02:16:46
12,Mark Messmer,02:17:27
13,Shadrack Biwott,02:17:36
14,Hitomi Niiya,02:19:24
15,David Fuentes,02:20:28
16,Patrice Labonte,02:20:49
17,Joseph Niemiec,02:21:06
18,Jesse Joseph,02:21:09
19,Aaron Davidson,02:21:40
20,Stan Linton,02:21:43
21,Mitchell Klingler,02:21:53
22,Michael Babinec,02:22:52
23,Tom Derr,02:23:07
24,Matt Dynan,02:23:33
25,Alexander Diltz,02:23:41
CSV_FILE_NAME = 'houston_marathon_2023.csv'
# Contents of the CSV file are above
def parse_data_as_df():
df = pd.read_csv(CSV_FILE_NAME, encoding='latin-1')
df['time'] = pd.to_datetime(df['time']).dt.time
return df
def create_histogram(cutoff_time):
BIN_SIZE_MINUTES = 5
MIN_TIME = dt.time(hour=2, minute=0, second=0)
MAX_TIME = dt.time(hour=6, minute=10, second=0)
time_increment = MIN_TIME
time_bins = {}
data = parse_data_as_df()
# get bins for original
while time_increment <= MAX_TIME:
next_time = dt.time(hour=time_increment.hour + int((time_increment.minute + BIN_SIZE_MINUTES) / 60),
minute=(time_increment.minute + BIN_SIZE_MINUTES) % 60,
second=0)
time_bins[time_increment] = 0
for a_time in data['time']:
if time_increment <= a_time < next_time:
time_bins[time_increment] += 1/len(data['time'])
time_increment = next_time
df = pd.DataFrame({'bin_times': time_bins.keys(), 'percent': time_bins.values()})
df["color"] = np.select(
[df["bin_times"].lt(cutoff_time)],
["#fd7e14"],
"#158cba",
)
load_figure_template(['lumen'])
fig = px.bar(
df,
x="bin_times",
y="percent",
color="color",
color_discrete_map={
"#fd7e14": "#fd7e14",
"#158cba": "#158cba",
},
template='lumen',
hover_data={
'color': False,
'percent': ':.1%'
}
)
fig.update_xaxes(tickformat='%H:%M',
ticktext=[d.strftime('%H:%M') for d in df['bin_times']])
fig.update_yaxes(tickformat='0%',
range=[0, 0.05])
fig.update_layout(xaxis_title="Time (HH:MM)",
yaxis_title="Density (%)",
showlegend=False,
bargap=0.025)
return fig
def create_dash_application():
APP_STYLESHEET = "https://cdn.jsdelivr.net/npm/bootswatch#5.2.3/dist/lux/bootstrap.min.css"
DEFAULT_CUTOFF_TIME = dt.time(hour=3)
dash_app = Dash(__name__, external_stylesheets=[APP_STYLESHEET])
dash_app.layout = html.Div([
html.Br(),
dcc.Graph(id='histogram_graph',
figure=create_histogram(DEFAULT_CUTOFF_TIME),
config={'displayModeBar': False},
animate=True),
html.Br(),
html.Div(
[
html.B('Hours: '),
dcc.Input(id='time_hours', value='', type='text', style={'width': '25%'}),
]),
html.Br(),
html.Div(
[
html.B('Minutes: '),
dcc.Input(id='time_minutes', value='', type='text', style={'width': '25%'}),
]),
html.Br(),
html.Div(
[
html.B('Seconds: '),
dcc.Input(id='time_seconds', value='', type='text', style={'width': '25%'}),
]),
html.Br(),
html.Button(children='Analyse', id='button_submit', n_clicks=0, style={'width': '25%'}, type='button'),
], style={'width': '75%'})
#dash_app.callback(
Output('histogram_graph', 'figure'),
[Input('button_submit', 'n_clicks'),
Input('time_hours', 'value'),
Input('time_minutes', 'value'),
Input('time_seconds', 'value')],
prevent_initial_call=True)
def update_output(n_clicks, the_hours, the_minutes, the_seconds):
if n_clicks > 0 and the_hours != '' and the_minutes != '' and the_seconds != '':
in_time = dt.time(hour=int(the_hours), minute=int(the_minutes), second=int(the_seconds))
# Something wrong here, as histogram is correctly generated but not changed in Dash App
return create_histogram(cutoff_time=in_time)
else:
return None
dash_app.run_server(debug=True)
create_dash_application()
I've isolated the issue to the callback returning a correct fig, that is not being updated in the app.layout. It is not an issue in the creation of fig, as confirmed by observing fig.show().
I am expecting fig in dcc.Graph to be updated.
What am I missing?
If you want to use the button to run the inputs, you need to switch those inputs to State. Please use below code to run:
import datetime as dt
import dash
CSV_FILE_NAME = 'houston_marathon_2023.csv'
# Contents of the CSV file are above
def parse_data_as_df():
df = pd.read_csv(CSV_FILE_NAME, encoding='latin-1')
df['time'] = pd.to_datetime(df['time']).dt.time
return df
def create_histogram(cutoff_time):
BIN_SIZE_MINUTES = 5
MIN_TIME = dt.time(hour=2, minute=0, second=0)
MAX_TIME = dt.time(hour=6, minute=10, second=0)
time_increment = MIN_TIME
time_bins = {}
data = parse_data_as_df()
# get bins for original
while time_increment <= MAX_TIME:
next_time = dt.time(hour=time_increment.hour + int((time_increment.minute + BIN_SIZE_MINUTES) / 60),
minute=(time_increment.minute + BIN_SIZE_MINUTES) % 60,
second=0)
time_bins[time_increment] = 0
for a_time in data['time']:
if time_increment <= a_time < next_time:
time_bins[time_increment] += 1/len(data['time'])
time_increment = next_time
df = pd.DataFrame({'bin_times': time_bins.keys(), 'percent': time_bins.values()})
df["color"] = np.select(
[df["bin_times"].lt(cutoff_time)],
["#fd7e14"],
"#158cba",
)
fig = px.bar(
df,
x="bin_times",
y="percent",
color="color",
color_discrete_map={
"#fd7e14": "#fd7e14",
"#158cba": "#158cba",
},
hover_data={
'color': False,
'percent': ':.1%'
}
)
fig.update_xaxes(tickformat='%H:%M',
ticktext=[d.strftime('%H:%M') for d in df['bin_times']])
fig.update_yaxes(tickformat='0%',
range=[0, 0.05])
fig.update_layout(xaxis_title="Time (HH:MM)",
yaxis_title="Density (%)",
showlegend=False,
bargap=0.025)
return fig
def create_dash_application():
APP_STYLESHEET = "https://cdn.jsdelivr.net/npm/bootswatch#5.2.3/dist/lux/bootstrap.min.css"
DEFAULT_CUTOFF_TIME = dt.time(hour=3)
dash_app = dash.Dash(__name__, external_stylesheets=[APP_STYLESHEET])
dash_app.layout = html.Div([
html.Br(),
dcc.Graph(id='histogram_graph',
figure=create_histogram(DEFAULT_CUTOFF_TIME),
config={'displayModeBar': False},
animate=True),
html.Br(),
html.Div(
[
html.B('Hours: '),
dcc.Input(id='time_hours', value='', type='text', style={'width': '25%'}),
]),
html.Br(),
html.Div(
[
html.B('Minutes: '),
dcc.Input(id='time_minutes', value='', type='text', style={'width': '25%'}),
]),
html.Br(),
html.Div(
[
html.B('Seconds: '),
dcc.Input(id='time_seconds', value='', type='text', style={'width': '25%'}),
]),
html.Br(),
html.Button(children='Analyse', id='button_submit', n_clicks=0, style={'width': '25%'}, type='button'),
], style={'width': '75%'})
#dash_app.callback(
Output('histogram_graph', 'figure'),
[Input('button_submit', 'n_clicks')],
[State('time_hours', 'value'),
State('time_minutes', 'value'),
State('time_seconds', 'value')],
prevent_initial_call=True)
def update_output(n_clicks, the_hours, the_minutes, the_seconds):
if n_clicks > 0 and the_hours != '' and the_minutes != '' and the_seconds != '':
in_time = dt.time(hour=int(the_hours), minute=int(the_minutes), second=int(the_seconds))
# Something wrong here, as histogram is correctly generated but not changed in Dash App
return create_histogram(cutoff_time=in_time)
else:
return None
dash_app.run_server(debug=True)
create_dash_application()
Hope this help
Related
I am trying to use the hoverData of a plot with many traces to display a side table of values related to each trace. The main code runs as follows. (note this is not the full code, but i included the relevant info)
def plots(self,):
df_lists = self.df_lists
plots_names = ['weakness', 'std', 'std_average', 'std_weak', 'p_average', 'p_repitition_average', 'p_median','p_median_all', 'p_median_average','p_range', 'p_range_average']
colors = {'background': '#111111', 'text': '#7FDBFF'}
from dash import Dash, dcc, html, Input, Output, State
names = self.names
app = Dash()
app.layout = html.Div( children=[
html.H4('Dieharder Tests Plots'),
html.P('Chose Plot Type'),
dcc.RadioItems(plots_names, plots_names[0], id="plot-picker", ),
html.P('Test Description'),
dcc.Markdown(id='test-explain', link_target="_blank", ),
html.P("Filter by test:"),
dcc.Dropdown(names, names[0], id="test-picker", multi = True),
dcc.Graph(id="plot", style={'width':'75%', 'float': 'left','height': '70vh','display':'inline-block'}),
html.Div([dcc.Graph(id='hover-data', style ={'float':'right'})], style={'width':'20%', 'paddingTop':35}),
])
#app.callback(
Output("plot", "figure"),
[Input("plot-picker", "value"), Input("test-picker", "value")])
def update_bar_chart(plot_picker, picker_test):
i=0
if plot_picker == 'weakness':
data = []
for test in picker_test:
df = df_lists[test]
p_value = [x for x in df.columns if x.startswith('pva')]
n_rounds = len(p_value)
trace = go.Bar(x=df.test_name, y = df.weak_rate, name = '{}, #rounds: {}'.format(test,n_rounds))
data.append(trace)
layout = go.Layout(title = 'Fraction of weak and failed results per each Dieharder test')
fig = go.Figure(data, layout)
fig.update_yaxes(title_text='Failed/weak fractions')
fig.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01))
return fig
The hover data includes the number of the trace not its name, which i need to specify the df source of the data. I am using the following code to get the hover data to generate the table:
#app.callback(Output('hover-data', 'graph'),
[Input('plot', 'hoverData')] )
def hover_data(hoverData):
Die_test = hoverData['points'][0]['x']
curve_number = hoverData['points'][0]['curveNumber']
trace_name = app.layout['plot'].figure['data'][curve_number]['name']
df = df_lists[trace_name]
df = df[df['test_name'] == Die_test]
data = [go.Table(header=dict(values=['p_mean', 'p_median', 'range', 'std'], fill_color='paleturquoise', align='left'), cells=dict(values=[df['p_mean'], df['p_median'], df['range'], df['std']] ))]
fig = go.Figure(data,)
return fig
The problem it is not working. I am not seeing anything when i hover over the data. I am not sure where the problem is coming, but most probably from the trace_name variable as i am getting the error:
Callback error updating hover-data.graph
AttributeError: 'Graph' object has no attribute 'figure'.
I tried to include a [State('plot', 'figure')] in the input of the callback. and then use the .figure['data'][curve_number]['name'] directly (instead of using app.layout['plot'] first), but it also didn't work.
Any help is appreciated.
Thanks
I don't have your dataframe so I think you can refer my code to revise yours:
from dash import Dash, html, dcc, Input, Output
import pandas as pd
import plotly.express as px
import dash_bootstrap_components as dbc
app = Dash(__name__, external_stylesheets=[dbc.themes.LUX])
df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')
app.layout = html.Div([
dbc.Row([
dbc.Col([
dcc.Dropdown(
df['Indicator Name'].unique(),
'Fertility rate, total (births per woman)',
id='crossfilter-xaxis-column',
),
dcc.RadioItems(
['Linear', 'Log'],
'Linear',
id='crossfilter-xaxis-type',
labelStyle={'display': 'inline-block', 'marginTop': '5px'}
)
], width={'size': 6, "offset": 0, 'order': 1}),
dbc.Col([
dcc.Dropdown(
df['Indicator Name'].unique(),
'Life expectancy at birth, total (years)',
id='crossfilter-yaxis-column'
),
dcc.RadioItems(
['Linear', 'Log'],
'Linear',
id='crossfilter-yaxis-type',
labelStyle={'display': 'inline-block', 'marginTop': '5px'}
)
], width={'size': 6, "offset": 0, 'order': 1})
], style={'padding': '10px 5px'}, className='p-2 align-items-center'),
dbc.Row([
dbc.Col([
dcc.Graph(
id='crossfilter-indicator-scatter',
hoverData={'points': [{'customdata': 'Japan'}]}
)
], width={'size': 6, "offset": 0, 'order': 1}),
dbc.Col([
dash_table.DataTable(id='table',
columns=[{"name": i, "id": i} for i in df.columns],
data=[],
style_table={'height': 550},
style_header={'backgroundColor': 'orange', 'padding': '10px', 'color': '#000000'},
style_cell={'textAlign': 'center', 'font_size': '12px',
'whiteSpace': 'normal', 'height': 'auto'},
editable=True, # allow editing of data inside all cells
filter_action="native", # allow filtering of data by user ('native') or not ('none)
sort_action="native", # enables data to be sorted per-column by user or not ('none')
sort_mode="single", # sort across 'multi' or 'single' columns
column_selectable="multi", # allow users to select 'multi' or 'single' columns
row_selectable="multi", # allow users to select 'multi' or 'single' rows
row_deletable=True, # choose if user can delete a row (True) or not (False)
selected_columns=[], # ids of columns that user selects
selected_rows=[], # indices of rows that user selects
page_action="native",
export_headers='display')
], width={'size': 6, "offset": 0, 'order': 1}),
], className='p-2 align-items-center'),
dbc.Row([
dbc.Col([
dcc.Slider(
df['Year'].min(),
df['Year'].max(),
step=None,
id='crossfilter-year--slider',
value=df['Year'].max(),
marks={str(year): str(year) for year in df['Year'].unique()}
)
], width={'size': 6, "offset": 0, 'order': 1})
], className='p-2 align-items-center')
])
#app.callback(
Output('crossfilter-indicator-scatter', 'figure'),
Input('crossfilter-xaxis-column', 'value'),
Input('crossfilter-yaxis-column', 'value'),
Input('crossfilter-xaxis-type', 'value'),
Input('crossfilter-yaxis-type', 'value'),
Input('crossfilter-year--slider', 'value'))
def update_graph(xaxis_column_name, yaxis_column_name,
xaxis_type, yaxis_type,
year_value):
dff = df[df['Year'] == year_value]
fig = px.scatter(x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
hover_name=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])
fig.update_traces(customdata=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])
fig.update_xaxes(title=xaxis_column_name, type='linear' if xaxis_type == 'Linear' else 'log')
fig.update_yaxes(title=yaxis_column_name, type='linear' if yaxis_type == 'Linear' else 'log')
fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')
return fig
#app.callback(
Output('table', 'data'),
Input('crossfilter-indicator-scatter', 'hoverData'),
Input('crossfilter-xaxis-column', 'value'),
Input('crossfilter-xaxis-type', 'value'))
def update_y_timeseries(hoverData, xaxis_column_name, axis_type):
country_name = hoverData['points'][0]['customdata']
dff = df[df['Country Name'] == country_name]
dff = dff[dff['Indicator Name'] == xaxis_column_name]
return dff.to_dict(orient='records')
if __name__ == '__main__':
app.run_server(debug=False, port=1414)
I think instead of return go.Table, you can use dash_table in your Div and then return filtered data frame. Hope this help.
I have a dash app that plots a dataframe which has a date component, and an entry that is either true or false. There are two graphs in the dashboard, one with the data vs date, and one with a percentage of True/False like below:
I can zoom in on the date range and select a subset clicking with the mouse.
I would like to feed this range back into the second graph.
At the moment to produce the above dashboard the relevant part of the code looks like:
from re import template
import pandas as pd
import plotly.express as px
from dash import Dash, Input, Output, dcc, html
from flask import globals
def init_dashboard(server):
evicted_df = pd.read_csv("app/data/evicted_jobs_node.csv", sep="\t")
all_df = pd.read_csv("app/data/all_jobs_node.csv", sep="\t")
all_df["datetime"] = pd.to_datetime(all_df["datetime"])
all_df = all_df.set_index(["datetime"])
all_df["evicted"] = all_df["id_job"].isin(evicted_df["id_job"])
app = Dash(__name__, server=server, routes_pathname_prefix="/dash/")
app.layout = html.Div(
[
html.Div(
className="row",
children=[
html.Div(
className="six columns",
children=[dcc.Graph(id="graph-with-dropdown")],
style=dict(width="75%"),
),
html.Div(
className="six columns",
children=[dcc.Graph(id="graph-with-dropdown2")],
style=dict(width="25%"),
),
],
style=dict(display="flex"),
),
html.Div(
className="row",
children=[
html.Div(
className="six columns",
children=[
dcc.Dropdown(
id="partition-dropdown",
options=[
"Partition (default is all)",
*all_df["partition"].unique(),
],
value="Partition (default is all)",
clearable=False,
searchable=False,
)
],
style={
"width": "50%",
"justify-content": "center",
},
),
html.Div(
className="six columns",
children=[
dcc.Dropdown(
id="node-dropdown",
options=[
"Number of Nodes (default is all)",
*sorted(
[
int(nodes)
for nodes in all_df["nodes_alloc"].unique()
]
),
],
value="Number of Nodes (default is all)",
clearable=False,
searchable=False,
)
],
style=dict(width="50%"),
),
],
style=dict(display="flex"),
),
]
)
init_callbacks(app, df, all_df)
return app.server
def init_callbacks(app, df, all_df):
#app.callback(
Output("graph-with-dropdown2", "figure"),
[Input("node-dropdown", "value"), Input("partition-dropdown", "value")],
)
def update_evicted_fig(selected_nodes, selected_partition):
if selected_nodes != "Number of Nodes (default is all)":
filtered_df = all_df[all_df["nodes_alloc"] == selected_nodes]
else:
filtered_df = all_df
if selected_partition != "Partition (default is all)":
filtered_df = filtered_df[filtered_df["partition"] == selected_partition]
x = ["Not Evicted", "Evicted"]
df1 = filtered_df.groupby(["evicted"]).count().reset_index()
fig = px.bar(
df1,
y=[
100
* filtered_df[filtered_df["evicted"] == False].size
/ filtered_df.size,
100
* filtered_df[filtered_df["evicted"] == True].size
/ filtered_df.size,
],
x=x,
color="evicted",
color_discrete_map={True: "red", False: "green"},
labels={"x": "Job Status", "y": "% of Jobs"},
)
fig.update_layout(transition_duration=500)
return fig
#app.callback(
Output("graph-with-dropdown", "figure"),
[Input("node-dropdown", "value"), Input("partition-dropdown", "value")],
)
def update_evicted_fig(selected_nodes, selected_partition):
if selected_nodes != "Number of Nodes (default is all)":
filtered_df = all_df[all_df["nodes_alloc"] == selected_nodes]
else:
filtered_df = all_df
if selected_partition != "Partition (default is all)":
filtered_df = filtered_df[filtered_df["partition"] == selected_partition]
print(
filtered_df[filtered_df["evicted"] == True]
.groupby([pd.Grouper(freq="6H")])
.sum(numeric_only=True)["node_hours"]
)
fig = px.bar(
x=filtered_df[filtered_df["evicted"] == False]
.groupby([pd.Grouper(freq="6H")])
.sum(numeric_only=True)["node_hours"]
.index,
y=filtered_df[filtered_df["evicted"] == False]
.groupby([pd.Grouper(freq="6H")])
.sum(numeric_only=True)["node_hours"],
labels={
"x": "Date",
"y": "Node hours",
},
title="Job Status",
barmode="stack",
)
fig.add_bar(
name="Evicted",
x=filtered_df[filtered_df["evicted"] == True]
.groupby([pd.Grouper(freq="6H")])
.sum(numeric_only=True)["node_hours"]
.index,
y=filtered_df[filtered_df["evicted"] == True]
.groupby([pd.Grouper(freq="6H")])
.sum(numeric_only=True)["node_hours"],
)
fig.update_layout(transition_duration=500)
return fig
return app.server
Is what I am hoping to do possible, and if so is there some documentation or a worked example someone could highlight for me?
I don't have you df so maybe you can refer my code to revise yours:
import pandas as pd
import numpy as np
import plotly.express as px
import dash
import dash_html_components as html
from dash import dcc
from dash_extensions.enrich import Input, Output, State, ServersideOutput
import dash_bootstrap_components as dbc
from dash.exceptions import PreventUpdate
df_2 = df[(df['BAS_DT'] >= '2022-01-01')]
df5 = df_2.pivot_table(values='USD_XC_BL',
index=['BAS_DT'],
aggfunc=np.sum).reset_index()
fig_3 = px.bar(df5,
x='BAS_DT',
y='USD_XC_BL',
labels='BAS_DT',
hover_name='BAS_DT', color_discrete_sequence=px.colors.qualitative.Alphabet)
fig_3.update_layout(xaxis_title="", yaxis_title="", plot_bgcolor='rgba(0,0,0,0)', margin=dict(l=0, r=0, t=0, b=0))
fig_3.update_xaxes(showline=False, showgrid=False),
fig_3.update_yaxes(showline=False, showgrid=False, separatethousands=True, tickformat=',.0f')
app = dash.Dash(__name__)
app.layout = html.Div([
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardBody([
dbc.Row([
dbc.Col([
html.H5('Amount by Currency', style={"text-align": "center"}),
dcc.Loading(children=[dcc.Graph(id='histogram_map', figure=fig_3)], color='#119DFF',
type='dot')
], width={'size': 12, 'offset': 0, 'order': 2}, style={"text-align": "left"}),
]),
])
]),
], xs=6),
dbc.Col([
dbc.Card([
dbc.CardBody([
dbc.Row([
dbc.Col([
html.H5('Overdue Status', style={"text-align": "center"}),
dcc.Loading(children=[dcc.Graph(id='overdue_map', figure={})], color='#119DFF', type='dot')
], width={'size': 12, 'offset': 0, 'order': 2}, style={"text-align": "left"}),
]),
])
]),
], xs=6),
], className='p-2 align-items-stretch')
])
#app.callback(
Output('overdue_map', 'figure'),
Input('histogram_map', 'clickData'))
def update_y_timeseries(clickData):
if clickData:
country_name = clickData['points'][0]['hovertext']
df_3 = df[df['BAS_DT'] == country_name]
df_4 = df_3.pivot_table(values='CLOC_CUR_XC_BL',
index=['APL_DTL_NAME'],
aggfunc=pd.Series.nunique).reset_index()
fig = px.bar(df_4,
x='APL_DTL_NAME',
y='CLOC_CUR_XC_BL'
, color_discrete_sequence=px.colors.qualitative.Alphabet)
fig.update_layout(xaxis_title="", yaxis_title="", plot_bgcolor='rgba(0,0,0,0)') # plot_bgcolor='rgba(0,0,0,0)'
fig.update_xaxes(showline=False, showgrid=False),
fig.update_yaxes(showline=False, showgrid=False, separatethousands=True)
fig.update_traces(width=0.3)
return fig
else:
raise PreventUpdate
if __name__ == "__main__":
app.run_server(debug=True)
I'm using clickData to return point as date, and then use this date to filter data and then make new bar graph.
Hope this help.
How to display all the previous month data with the selected date's data in plotly python?
Let says selected date, 15/03/2022, so, the bar chart should show all the previous whole month data (Jan, Feb) and for Mar, data should be show month-to-date data (01/03/2022 to 15/03/2022).
This is what I can get now, but it is not what I want. For Jan and Feb, the data is correct as it taking the whole month data, but for the Mar, it also taking the whole month data instead of month-to-date.
enter image description here
Code as per below:
(1) this code will get month-to-date data (01/03/2022 to 15/03/2022)
month_category = list(df['Month'].unique())
date_category = list(df['Settlement_Date'].unique())
bar_groupby = df.groupby(['Settlement_Date','Date','Month'])['MTD'].agg(['sum']).reset_index().rename(columns={'sum':'Total_Tx_Amount'})
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
html.H3('Month'),
html.Br(),
dcc.Dropdown(id='month_dd', value= 'Jan',
options = [{'label':x, 'value':x}
for x in month_category],
searchable = True, search_value='',
placeholder= 'Please select ...'
),
html.Br(),
html.H3('Date'),
html.Br(),
dcc.Dropdown(id='date_dd')
])
]),
dbc.Row([
dbc.Col([
html.P("Bar Chart:",
style={"textDecoration":"underline"}),
dcc.Graph(id='bar-fig', figure={})
])
])
])
#app.callback(
Output('date_dd','options'),
Input('month_dd', 'value')
)
def update_dd (month_dd):
month_date= df.drop_duplicates(['Month','Settlement_Date'], inplace= False)
relevant_date= month_date[month_date['Month']== month_dd]['Settlement_Date'].values.tolist()
date_option= [dict(label=x,value=x)for x in relevant_date]
return date_option
#app.callback(
Output('bar-fig', 'figure'),
Input('date_dd', 'value')
)
def update_graph(selection):
if len (selection) ==0:
return dash.no_update
else:
dff = bar_groupby[bar_groupby['Settlement_Date'] == selection]
fig = px.bar(dff, x='Month', y='Total_Tx_Amount', title='Bar_chart', color='Month')
return fig
(2) this code will get data for all the month (Jan, Feb, Mar)
month_category = list(df['Month'].unique())
date_category = list(df['Settlement_Date'].unique())
bar_groupby = df.groupby(['Settlement_Date','Date','Month'])['MTD'].agg(['sum']).reset_index().rename(columns={'sum':'Total_Tx_Amount'})
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
html.H3('Month'),
html.Br(),
dcc.Dropdown(id='month_dd', value= 'Jan',
options = [{'label':x, 'value':x}
for x in month_category],
searchable = True, search_value='',
placeholder= 'Please select ...'
),
html.Br(),
html.H3('Date'),
html.Br(),
dcc.Dropdown(id='date_dd')
])
]),
dbc.Row([
dbc.Col([
html.P("Bar Chart:",
style={"textDecoration":"underline"}),
dcc.Graph(id='bar-fig', figure={})
])
])
])
#app.callback(
Output('date_dd','options'),
Input('month_dd', 'value')
)
def update_dd (month_dd):
month_date= df.drop_duplicates(['Month','Settlement_Date'], inplace= False)
relevant_date= month_date[month_date['Month']== month_dd]['Settlement_Date'].values.tolist()
date_option= [dict(label=x,value=x)for x in relevant_date]
return date_option
#app.callback(
Output('bar-fig', 'figure'),
Input('date_dd', 'value')
)
def update_graph(selection):
if len (selection) ==0:
return dash.no_update
else:
fig = px.bar( bar_groupby, x='Month', y='Total_Tx_Amount', title='Bar_chart', color='Month')
return fig
both code getting the correct data, but I m not able to join both data into a same chart.
Can anyone assist or advise on this?
How to display the data in minor_pie_chart based on the hover data in the major_pie_chart?
Let say the data as per below:
enter image description here
There will have two pie chart, one is major_pie_chart and another one is minor_pie_chart.
The data in the minor_pie_chart will be based on the hover data in the major_pie_chart.
date_category = list(df['Date'].unique())
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
html.P("Pie Chart:",
style={"textDecoration":"underline"}),
dcc.Dropdown(id='date_drdn', multi=False, value= '01/01/2022',
options = [{'label':x, 'value':x}
for x in date_category]
),
dcc.Graph(id='pie-fig', figure={},style={'display': 'inline-block'})
])
]),
dbc.Row([
dbc.Col([
html.P("Pie Chart Detail:",
style={"textDecoration":"underline"}),
dcc.Graph(id='pie_detail', style={'display': 'inline-block'})
])
])
])
#app.callback(
Output('pie-fig', 'figure'),
Input('date_drdn', 'value')
)
def update_graph(selection):
if len (selection) ==0:
return dash.no_update
else:
dff = df[df['Date'] == selection]
fig = px.pie(dff, values='Transactions', names='Major', color_discrete_sequence=px.colors.sequential.RdBu)
fig.update_traces(textinfo= "label+value+percent").update_layout(title_x=0.5)
return fig
#app.callback(
Output('pie_detail', 'figure'),
Input('pie-fig', 'hoverData')
)
def update_pie_detail_hover(hoverData):
hover_info = 'Clothes'
if hoverData:
hover_info = hoverData['points'][0]['customdata'][0]
detail_df = df[df['Major'] == hover_info]
fig = px.pie(detail_df, values='Transactions', names='Minor', color_discrete_sequence=px.colors.sequential.RdBu)
fig.update_traces(textinfo= "label+value+percent").update_layout(title_x=0.5)
return fig
I am trying to apply callbacks between different dash pages. I have setup my app components in different files:
Hotspot_App.py
index.py
folder apps
GeoPlot.py
ScatterPlot.py
So the first page contains the layout of app1 and it's callbacks and components. But now I want to create a checklist in the second tab on the values that have been selected on a checklist in tab1. I tried to create the app.validation layout in the index but that does not work. See below my code:
Index.py
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_auth
import dash_core_components as dcc
from Hotspot_App_Local import app
from apps import GeoPlot, ScatterPlot
app.title = 'Hotspot Analysis'
auth = dash_auth.BasicAuth(
app,
VALID_USERNAME_PASSWORD_PAIRS
)
scr_oord = 'www.animage.com' #dummy
nav_item = dbc.NavItem(dbc.NavLink("website.com", href="https://www.website.com/"))
dropdown_menu = dbc.DropdownMenu(
children=[
dbc.DropdownMenuItem("GeoPlot", href= "/page-1"),
dbc.DropdownMenuItem("ScatterPlot", href= "/page-2"),
],
nav=True,
in_navbar=True,
label="Menu",
)
navbar = html.Div([dbc.Navbar(
dbc.Container(
[
html.A(
# Use row and col to control vertical alignment of logo / brand
dbc.Row(
[
dbc.Col(html.Img(src= scr_oord, height="50px")),
dbc.Col(dbc.NavbarBrand("\tHotspot Analysis", className="ml-2", style= {'color': 'navy', 'font-weight': 'bold'})),
],
align="center",
no_gutters=True,
),
href="https://plot.ly",
),
dbc.NavbarToggler(id="navbar-toggler2"),
dbc.Collapse(
dbc.Nav(
[nav_item, dropdown_menu], className="ml-auto", navbar=True
),
id="navbar-collapse2",
navbar=True,
),
]
),
color="white",
dark= False,
className="mb-5",
),
html.Div(id= 'page_content')
])
app.layout = html.Div([
dcc.Location(id= 'tab', refresh= False, pathname= "/page-1"),
navbar,
html.Div(id= 'page')
])
app.validation_layout = html.Div([navbar, GeoPlot.layout, ScatterPlot.layout])
#app.callback(Output("page", "children"), [Input("tab", "pathname")])
def display_page(pathname):
if pathname == "/page-1":
return GeoPlot.layout
if pathname == "/page-2":
return ScatterPlot.layout
# if not recognised, return 404 message
return html.P("404 - page not found")
if __name__ == '__main__':
app.run_server(debug=True)
app.py
import dash_bootstrap_components as dbc
import dash
app = dash.Dash(__name__, suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.BOOTSTRAP])
server = app.server
Geoplot.py
import dash_bootstrap_components as dbc
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State, ALL, MATCH
import plotly.graph_objects as go
import pandas as pd
import flask
import ast
from flask import send_file
import io
import datetime
from Hotspot_App_Local import app
from Cards import Cards
df = pd.read_csv('merged_layers.csv')
df = df.round(2)
available_indicators = ['PercBelowMSL', 'PopDens2015', 'RPC4.5', 'PortDist', 'RPC8.5']
df['text'] = 'TotPop2020: '+ round(df["TotPop2020"], 0).astype(str)
Storedata = dcc.Store(id= 'store_columns')
Dropdown = dcc.Dropdown(id= 'Chose setting',
options= [
{'label': 'Marine', 'value': 'Marine'},
{'label': 'Environmental', 'value': 'Env'},
{'label': 'Urban', 'value': 'Urban'},
{'label': 'Ports', 'value': 'Ports'}
], value= 'Marine', clearable= False)
card2 = Cards('Select The Setting', [Storedata, Dropdown], Header= True)
checkbox = dbc.Checklist(id = 'col_list')
card3 = Cards('Select The Layers', [checkbox], Header= True)
htmlA = html.A([dbc.Button("Store Values", color= 'success', block= True)], id= 'store-values')
#csvbutton = dbc.Button('Store Values', id= 'csv', block= True)
card4 = Cards('', [htmlA])
sizer = 2 * df['PopDens2020'].max() / (30 ** 2)
fig = go.Figure(data=go.Scattermapbox(
lon = df['center_lon'],
lat = df['center_lat'],
text = df['text'],
mode = 'markers',
marker= go.scattermapbox.Marker(
color= df['PopDens2020'],
cmin = df['PopDens2020'].min(),
cmax= df['PopDens2020'].max(),
colorscale = 'Viridis',
colorbar=dict(
title="PopDens2020"
),
showscale= True,
size= df['PopDens2020'],
sizeref= sizer,
sizemode= 'area'
),
))
fig.update_layout(
title= {'text': f'{len(df)} Hotspot Locations',
'font': {
'size': 36,
'color': 'darkblue'
},
'xref': 'container',
'x': 0.5},
geo_scope='world',
mapbox_style= 'open-street-map',
width= 1200,
height= 800
)
hotspotgraph = dcc.Graph(id= 'Hotspot_Plot', figure= fig)
card5 = Cards("", [hotspotgraph], Header= False)
rangeslider = html.Div(id='RangeSlider', children=[])
card6 = Cards("Select the Range", [rangeslider], Header= True)
size = dcc.Dropdown(id= 'markersize')
card7 = Cards('Select Markersize', [size], Header= True)
color = dcc.Dropdown(id= 'markercolor')
card8 = Cards('Select Markercolor', [color], Header= True)
layout = html.Div(
[
dbc.Row(
[
dbc.Col([
dbc.Col(dbc.Card(card2, color="light", outline=True)),
dbc.Col(dbc.Card(card3, color="light", outline=True)),
dbc.Col(dbc.Card(card4, color="light", outline=True)),
dbc.Row([
dbc.Col(dbc.Card(card7, color="light", outline= True)),
dbc.Col(dbc.Card(card8, color="light", outline= True))
])
]),
dbc.Col(dbc.Card(card5, color="light", outline=True)),
],
className="mb-4",
),
dbc.Row(
[
dbc.Col(dbc.Card(card6, color="light", outline=True), md= 12),
],
className="mb-4",
),
]
)
#app.callback(
Output('store_columns', 'data'),
[Input('Chose setting', 'value')]
)
def change_cols(value):
if value == 'Marine':
data = ['PortDist', 'PercBelow-5m', 'RPC4.5', 'RPC8.5']
if value == 'Urban':
data = ['PopDens2020', 'area']
if value == 'Env':
data = ['ManPc2016', 'WWCrAr2018', 'count_PA', 'NearestDist_PA']
if value == 'Ports':
data = ['RPC4.5', 'PortDist']
return data
#app.callback(
Output('col_list', 'options'),
[Input('store_columns','data')],
)
def change_multiselect(data):
if data is not None:
return [{'label': i, 'value': i} for i in sorted(data)]
#app.callback(
Output('col_list', 'value'),
[Input('store_columns','data')]
)
def change_multiselect(data):
return sorted(data)
#app.callback(
Output('markersize', 'options'),
[Input('col_list','value')]
)
def markersize(values):
default = ['PopDens2020']
default.extend(values)
return [{'label': value, 'value': value} for value in default]
#app.callback(
Output('markersize', 'value'),
[Input('col_list','value')]
)
def markersize(values):
return 'PopDens2020'
#app.callback(
Output('markercolor', 'options'),
[Input('col_list','value')]
)
def markersize(values):
default = ['PopDens2020']
default.extend(values)
return [{'label': value, 'value': value} for value in default]
#app.callback(
Output('markercolor', 'value'),
[Input('col_list','value')]
)
def markersize(values):
return 'PopDens2020'
#app.callback(
Output('RangeSlider', 'children'),
[Input('col_list', 'value')],
[State('RangeSlider', 'children')])
def display_dropdowns(value, ranges):
df = pd.read_csv('merged_layers.csv')
lst = []
if value != None:
for v in value:
fig = go.Figure()
fig.add_trace(go.Histogram(x = df[v]))
fig.add_shape(
dict(
type='line',
yref='paper', y0=0, y1=1,
x0=df[v].median(), x1=df[v].median()
)
)
hist = dcc.Graph(id={'type': 'histogram', 'index': v}, figure=fig)
reset_button = dbc.Button('Reset Values',id={'type': 'reset_button', 'index': v}, block= True, color= 'danger')
card_button = Cards("", [reset_button])
min = df[v].min()
max = df[v].max()
range_slider = dcc.RangeSlider(
id={
'type': 'range_slider',
'index': v
}, tooltip={'always_visible': True, 'placement': 'bottomRight'}, min=min, max=max, value=[min, max], step= 0.01)
store_minmax = dcc.Store(id={'type': 'store', 'index': v}, data= [min, max])
card_slider = Cards(f'{v}', [range_slider, store_minmax], Header= True)
marklen = dcc.Markdown(children= f'**{len(df)} Hotspot Locations**', id={'type': 'len_df', 'index': v},
style= {'textAlign': 'center', 'color': 'darkblue', 'font-size': 25})
card_hist = Cards(f"", [marklen, hist])
ap = html.Div([
dbc.Row(
[
dbc.Col([
dbc.Col(dbc.Card(card_slider, color="light", outline=True)),
dbc.Col(dbc.Card(card_button, color= "light", outline= True))], md= 6),
dbc.Col(dbc.Card(card_hist, color="light", outline=True), md=6)
],
className="mb-4",
),
])
lst.append(ap)
return lst
#app.callback(
Output({'type': 'len_df', 'index': ALL}, 'children'),
[Input({'type': 'range_slider', 'index': ALL}, 'value')],
[State('col_list', 'value'),
State({'type': 'len_df', 'index': ALL}, 'children')]
)
def store_len_df(values, cols, text):
df = pd.read_csv('merged_layers.csv')
for i in range(len(cols)):
min = values[i][0]
max = values[i][1]
df = df[df[cols[i]].between(min, max)]
text = [f'**{len(df)} Hotspot Locations**'] * len(text)
return text
#app.callback(
Output({'type': 'histogram', 'index': MATCH}, 'figure'),
[Input({'type': 'range_slider', 'index': MATCH}, 'value')],
[State({'type': 'histogram', 'index': MATCH}, 'figure')])
def add_verts(range, fig):
fig['layout']['shapes'] = [{'type': 'line', 'x0': range[0], 'x1': range[0], 'y0': 0, 'y1': 1, 'yref': 'paper'},
{'type': 'line', 'x0': range[1], 'x1': range[1], 'y0': 0, 'y1': 1, 'yref': 'paper'}]
return fig
#app.callback(
Output({'type': 'range_slider', 'index': MATCH}, 'value'),
[Input({'type': 'reset_button', 'index': MATCH}, 'n_clicks')],
[State({'type': 'store', 'index': MATCH}, 'data')])
def reset(click, data):
return data
#app.callback(
Output('Hotspot_Plot', 'figure'),
[Input({'type': 'range_slider', 'index': ALL}, 'value'),
Input('markersize', 'value'),
Input('markercolor', 'value')],
[State('col_list', 'value'),
State('Hotspot_Plot', 'figure'),]
)
def update_figure(values, col_size, col_color, cols, fig):
if cols is not None and len(values) > 0 and len(cols) > 0:
df = pd.read_csv('merged_layers.csv')
if col_size != col_color:
df['text'] = f'{col_size}: ' + round(df[col_size], 0).astype(str) + '\n' + f'{col_color}: ' + round(df[col_color], 0).astype(str)
else:
df['text'] = f'{col_size}: ' + round(df[col_size], 0).astype(str)
size_series = df[col_size]
for value in df[col_size]:
if value < 0:
size_series += abs(df[col_size].min()) + 0.01 #No Negative Values Allowed
break
color_series = df[col_color]
sizer = 2 * df[col_size].max() / (30 ** 2)
for i in range(len(cols)):
min = values[i][0]
max = values[i][1]
df = df[df[cols[i]].between(min, max)]
fig['data'][0]['lat'] = df['center_lat']
fig['data'][0]['lon'] = df['center_lon']
fig['data'][0]['marker'] = go.scattermapbox.Marker(
color= color_series,
cmin = color_series.min(),
cmax= color_series.max(),
colorscale = 'Viridis',
colorbar=dict(
title= col_color
),
showscale=True,
size= size_series,
sizeref=sizer,
sizemode='area'
)
fig['data'][0]['text'] = df['text']
fig['layout']['title']['text'] = f'{len(df)} Hotspot Locations'
return fig
#app.callback(
Output('store-values', 'href'),
[Input('col_list', 'value'),
Input({'type': 'range_slider', 'index': ALL}, 'value'),
Input('Chose setting', 'value')])
def update_link(columns, ranges, setting):
return '/dash/urlToDownload?value={}*{}*{}'.format(columns, ranges, setting)
#app.server.route('/dash/urlToDownload')
def download_excel():
data = flask.request.args.get('value').split('*')
columns = ast.literal_eval(data[0])
ranges = ast.literal_eval(data[1])
setting = data[2]
minlst, maxlst= [], []
for r in ranges:
min, max = r
minlst.append(min)
maxlst.append(max)
range_df = pd.DataFrame(list(zip(columns, minlst, maxlst)), columns=['Column', 'Min_Value', 'Max_Value'])
df = pd.read_csv('Dash/merged_layers.csv')
for (i, range) in enumerate(ranges):
min, max = range
df = df[df[columns[i]].between(min, max)]
center_lon = df['center_lon'].values
center_lat = df['center_lat'].values
country = df['country'].values
loc_df = pd.DataFrame(list(zip(center_lon, center_lat, country)), columns= ['center_lon', 'center_lat', 'country'])
loc_df = loc_df.sort_values('country')
buf = io.BytesIO()
excel_writer = pd.ExcelWriter(buf, engine="xlsxwriter")
range_df.to_excel(excel_writer, sheet_name="Ranges", index=False)
loc_df.to_excel(excel_writer, sheet_name="Locations", index=False)
excel_writer.save()
excel_data = buf.getvalue()
buf.seek(0)
now = datetime.datetime.now()
today = str(now.year) + '-' + str(now.month) + '-' + str(now.day)+ ' ' + str(now.hour) + str(":") + str(now.minute)
return send_file(
buf,
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
attachment_filename=f"{setting}_{today}.xlsx",
as_attachment=True,
cache_timeout=0
)
Scatterplot.py
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ALL, MATCH
from Hotspot_App_Local import app
from Cards import Cards
test = dcc.Checklist(id= 'scatterlst')
card1 = Cards('Test', test, Header= True)
layout = html.Div(
dbc.Row(dbc.Card(card1, color="light", outline=True))
)
#app.callback(
Output('scatterlst', 'value'),
[Input('col_list','value')]
)
def change_multiselect(data):
print('hi')
print(data)
return sorted(data)
So the problem is that Scatterplot.py does not recognizes the indexes from Geoplot.py.