Hello everyone,
I am creating a live dashboard to visualise some data but unfortunately my data are not the latest in the app.
First of all my app also runs in a docker container using nginx.
So to give you more context I made an application that fetches and preprocess the data from an API and runs every 30 minutes in a cronjob.
The data finally are stored in a csv file locally and a have created a method for final data preparation.
The I call the data in my dash but they do not update until I restart the container.
I have tried dcc.Intervals but it did not work for me, either as a separate callback or inside where I produce the graph.
Strange is that if I manipulate the data directly on csv the changes are appearing in the app but if the new csv is created after the cronjob the newest data are there after docker restart!
Below you may find my application as well.
If you have any ideas, it would be highly appreciated!
Thanks a lot,
Panos
import dash
from dash import dcc, html, Input, Output, State, dash_table, no_update
import dash_daq as daq
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime as dt
import pandas as pd
import dash_bootstrap_components as dbc
from datetime import datetime, timedelta
from dataset import import_dataset
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
pd.set_option('mode.chained_assignment', None)
data_table_style = {
'font-family': 'Arial, sans-serif',
'font-size': '14px',
'text-align': 'left',
'margin-left': 'auto',
'margin-right': 'auto'
}
header_style = {
'font-family': 'Arial, sans-serif',
'backgroundColor': '#f2f2f2',
'fontWeight': 'bold',
'text-align': 'center'
}
row_style = {
'backgroundColor': '#ffffff'
}
selected_row_style = {
'backgroundColor': '#7bb9f6'
}
style_data_conditional=[
{
'if': {'row_index': 'odd'},
'backgroundColor': 'rgb(248, 248, 248)'
},
{
'if': {'row_index': 'even'},
'backgroundColor': 'white'
},
{
'if': {'column_id': 'Aufrufe'},
'color': 'black'
}
]
global df
df = import_dataset()
app = dash.Dash(__name__,
requests_pathname_prefix="/instr/graphs/usage/",
routes_pathname_prefix="/graphs/usage/",
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}],
external_stylesheets=[dbc.themes.BOOTSTRAP, "/static/styles.css"])
app.config.suppress_callback_exceptions = True
def extract_academic_years(df):
years = pd.to_datetime(df['day']).dt.year.unique()
academic_years = [f"{year}/{year + 1}" for year in years]
return academic_years
def usage_filters():
academic_years = extract_academic_years(df)
current_date = datetime.now()
if (current_date.month >= 9):
current_academic_year = f"{current_date.year}/{current_date.year + 1}"
else:
current_academic_year = f"{current_date.year - 1}/{current_date.year}"
return html.Div(
id="control-card",
children=[
html.H4("Bitte wählen Sie Ihre Filter aus", style={'textAlign': 'center'}),
html.Br(),
html.P("Learning Block auswählen"),
dcc.Dropdown(
id="LB-select",
placeholder='Learning Block auswählen',
multi=True,
),
html.Br(),
html.P("Learning Nugget auswählen"),
dcc.Dropdown(
id='nugget-dropdown',
multi=True,
placeholder='Learning Nugget auswählen',
),
html.Br(),
html.P("Datum auswählen"),
dcc.DatePickerRange(
id='date-picker-range',
start_date=datetime.today()-timedelta(days=14),
end_date=df['day'].max(),
display_format='DD.MM.YYYY',
style={'border': 0}
),
html.Br(),
html.Br(),
html.P("Anzahl der eindeutigen Nutzer zählen"),
daq.BooleanSwitch(
id='unique-counts-switch',
on=True,
style={'margin-bottom': '10px'}
),
html.Div([
dcc.Interval(
id='interval-component',
interval=10 * 60 * 1000,
n_intervals=0
),
html.Div(id='hidden-div', style={'display': 'none'}),
])
],
style={
'padding': '20px',
'border': '1px solid #ccc',
'border-radius': '5px',
'background-color': '#f9f9f9',
'box-shadow': '0 2px 4px rgba(0,0,0,0.1)',
'font-family': 'Arial, sans-serif',
'font-size': '14px',
'color': '#333'
}
)
app.layout = dbc.Container(
[
dcc.Location(id='url', refresh=False),
dbc.Row(
[
dbc.Col(usage_filters(), md=4),
dbc.Col(
[
dbc.Card(
[
dbc.CardHeader(html.B("Learning Nugget Nutzung")),
dcc.Loading(
id="loading-main-chart",
children=[
dbc.Card(
dbc.CardBody([dcc.Graph(id="bar-plot")])
)
],
type="default"
),
]
),
html.P("Ansicht:"),
dcc.Dropdown(
id='view-dropdown',
options=[
{'label': 'Learning Block', 'value': 'LB'},
{'label': 'Learning Nugget', 'value': 'nugget'},
{'label': 'Learning Nugget typ', 'value': 'LN_type'},
{'label': 'Schwierigkeit', 'value': 'difficulty'}
],
value='nugget'
),
html.Br(),
dbc.Button("Legende anzeigen/ausblenden", id="legend-toggle-btn", color="primary", className="mb-3"),
dbc.Card(
[
dbc.CardHeader(html.B("Nutzung pro Learning Nugget")),
dbc.CardBody(
[
dash_table.DataTable(
id="table",
columns=[
{"name": "Learning Nugget", "id": "nuggetName"},
{"name": "Aufrufe", "id": "Aufrufe"},
],
data=df.to_dict('records'),
style_table={'overflowX': 'auto'},
export_format="xlsx",
export_headers='display',
style_data=data_table_style,
style_header=header_style,
style_cell={'textAlign': 'center', 'padding': '10px'},
style_cell_conditional=[
{'if': {'column_id': 'nuggetName'}, 'width': '50%'},
{'if': {'column_id': 'Aufrufe'}, 'width': '50%'},
],
style_as_list_view=True,
selected_rows=[],
style_data_conditional=style_data_conditional,
page_action='native',
page_size=10,
)
]
)
]
)
],
md=8
),
]
),
dcc.Interval(
id='interval-component',
interval=10*60*1000,
n_intervals=0
)
],
fluid=True,
)
@app.callback(
Output('LB-select', 'options'),
Output('nugget-dropdown', 'options'),
[Input('LB-select', 'value')],
[State('url', 'pathname')]
)
def update_options(selected_LB, pathname):
course_id = pathname.split('courseid=')[-1]
filtered_df = df[df['courseid'] == int(course_id)]
lb_options = [{'label': lb, 'value': lb} for lb in filtered_df['name'].unique()]
nugget_options = [{'label': nugget, 'value': nugget} for nugget in filtered_df['nuggetName'].unique()]
if selected_LB:
filtered_df = filtered_df[filtered_df['name'].isin(selected_LB)]
nugget_options = [{'label': nugget, 'value': nugget} for nugget in filtered_df['nuggetName'].unique()]
return lb_options, nugget_options
@app.callback(
Output('view-dropdown', 'options'),
Output('view-dropdown', 'value'),
[Input('LB-select', 'value'),
Input('view-dropdown', 'value')],
prevent_initial_call=True
)
def update_view_options(selected_LB, current_view):
view_options = [
{'label': 'Learning Block', 'value': 'LB'},
{'label': 'Learning Nugget', 'value': 'nugget'},
{'label': 'Learning Nugget Typ', 'value': 'type'}
]
default_view = 'nugget'
if selected_LB:
view_options = [{'label': 'Learning Nugget', 'value': 'nugget'},
{'label': 'Learning Nugget Typ', 'value': 'type'}]
default_view = 'nugget'
if current_view not in [opt['value'] for opt in view_options]:
current_view = default_view
return view_options, current_view
@app.callback(
Output("bar-plot", "figure"),
[Input("date-picker-range", "start_date"),
Input("date-picker-range", "end_date"),
Input("unique-counts-switch", "on"),
Input("LB-select", "value"),
Input("nugget-dropdown", "value"),
Input('view-dropdown', 'value'),
Input('url', 'pathname'),
Input('interval-component', 'n_intervals'),
Input('legend-toggle-btn', 'n_clicks')]
)
def update_bar_plot(start_date, end_date, unique_counts, selected_LB, selected_nuggets, view, pathname, n_intervals, n_clicks):
if n_intervals:
global df
df = import_dataset()
print("I am updated!", datetime.now())
course_id = pathname.split('courseid=')[-1]
filtered_df = df[df['courseid'] == int(course_id)]
if start_date and end_date:
start_date, end_date = pd.to_datetime(start_date), pd.to_datetime(end_date)
filtered_df = filtered_df[
(filtered_df['day'] >= start_date.date()) & (filtered_df['day'] <= end_date.date())
]
if selected_LB:
filtered_df = filtered_df[filtered_df['name'].isin(selected_LB)]
if selected_nuggets:
filtered_df = filtered_df[filtered_df['nuggetName'].isin(selected_nuggets)]
if view == 'LB':
if unique_counts:
grouped_data = filtered_df.groupby(['day', 'name'])['user_id'].nunique().unstack(fill_value=0)
else:
grouped_data = filtered_df.groupby(['day', 'name']).size().unstack(fill_value=0)
x_label = 'Date'
y_label = 'Learning Block'
legend_title = 'Learning Block'
elif view == 'nugget':
if unique_counts:
grouped_data = filtered_df.groupby(['day', 'nuggetName'])['user_id'].nunique().unstack(fill_value=0)
else:
grouped_data = filtered_df.groupby(['day', 'nuggetName']).size().unstack(fill_value=0)
x_label = 'Date'
y_label = 'Learning Nugget'
legend_title = 'Learning Nugget'
else:
if unique_counts:
grouped_data = filtered_df.groupby(['day', 'type'])['user_id'].nunique().unstack(fill_value=0)
else:
grouped_data = filtered_df.groupby(['day', 'type']).size().unstack(fill_value=0)
x_label = 'Date'
y_label = 'Learning Nugget Typ'
legend_title = 'Learning Nugget Typ'
fig = px.bar(
grouped_data.reset_index(),
x='day',
y=grouped_data.columns,
barmode='stack',
labels={'day': 'Datum', 'value': 'Anzahl', 'variable': y_label},
)
fig.update_layout(
legend=dict(
title=legend_title,
font=dict(size=10),
orientation='v',
yanchor='middle',
y=0.5,
xanchor='right',
x=5,
)
)
fig.update_traces(hovertemplate='Day: %{x}<br>Count: %{y}')
show_legend = n_clicks is None or n_clicks % 2 == 0
fig.update_layout(showlegend=show_legend)
return fig
@app.callback(
[Output('table', 'data'),
Output('table', 'columns')],
[Input("date-picker-range", "start_date"),
Input("date-picker-range", "end_date"),
Input("LB-select", "value"),
Input("nugget-dropdown", "value"),
Input('unique-counts-switch', 'on'),
Input('view-dropdown', 'value'),
Input('interval-component', 'n_intervals'),
Input('url', 'pathname')]
)
def update_table(start_date, end_date, selected_LB, selected_nuggets, unique_counts, view, n_intervals, pathname):
course_id = pathname.split('courseid=')[-1]
filtered_df = df[df['courseid'] == int(course_id)]
if selected_LB:
filtered_df = filtered_df[filtered_df['name'].isin(selected_LB)]
if selected_nuggets:
filtered_df = filtered_df[filtered_df['nuggetName'].isin(selected_nuggets)]
if start_date and end_date:
start_date, end_date = pd.to_datetime(start_date), pd.to_datetime(end_date)
filtered_df = filtered_df[
(filtered_df['day'] >= start_date.date()) & (filtered_df['day'] <= end_date.date())
]
if view == 'LB':
if unique_counts:
grouped_data = filtered_df.groupby(['name', 'coursename'])['user_id'].nunique().reset_index()
sum_per_LB_course = grouped_data.groupby(['name', 'coursename'])['user_id'].sum().reset_index()
sum_per_LB_course.columns = ['name', 'coursename', 'Aufrufe']
columns = [
{"name": "Learning Block", "id": "name"},
{"name": "Kurs", "id": "coursename"},
{"name": "Learning Block Aufrufe", "id": "Aufrufe"}
]
else:
grouped_data = filtered_df.groupby(['name', 'coursename']).size().reset_index(name='Aufrufe')
sum_per_LB_course = grouped_data.groupby(['name', 'coursename'])['Aufrufe'].sum().reset_index()
columns = [
{"name": "Learning Block", "id": "name"},
{"name": "Kurs", "id": "coursename"},
{"name": "Learning Block Aufrufe", "id": "Aufrufe"}
]
data = sum_per_LB_course.to_dict('records')
elif view == 'nugget':
if unique_counts:
grouped_data = filtered_df.groupby(['nuggetName', 'name', 'coursename',
'type', 'difficulty'])['user_id'].nunique().reset_index()
sum_per_LN_course = grouped_data.groupby(['nuggetName', 'name', 'coursename',
'type', 'difficulty'])['user_id'].sum().reset_index()
sum_per_LN_course.columns = ['nuggetName', 'name', 'coursename', 'type', 'difficulty', 'Aufrufe']
columns = [
{"name": "Learning Nugget", "id": "nuggetName"},
{"name": "Learning Block", "id": "name"},
{"name": "Kurs", "id": "coursename"},
{"name": "Aufrufe", "id": "Aufrufe"},
{"name": "Schwierikeit", "id": "difficulty"},
{"name": "Typ", "id": "type"}
]
columns = [
{"name": "Learning Nugget", "id": "nuggetName"},
{"name": "Learning Block", "id": "name"},
{"name": "Kurs", "id": "coursename"},
{"name": "Aufrufe", "id": "Aufrufe"},
{"name": "Schwierikeit", "id": "difficulty"},
{"name": "Typ", "id": "type"}
]
else:
grouped_data = filtered_df.groupby(['nuggetName', 'name', 'coursename',
'type', 'difficulty']).size().reset_index(name='Aufrufe')
sum_per_LN_course = grouped_data.groupby(['nuggetName', 'name', 'coursename',
'type', 'difficulty'])['Aufrufe'].sum().reset_index()
sum_per_LN_course.columns = ['nuggetName', 'name', 'coursename', 'type', 'difficulty', 'Aufrufe']
columns = [
{"name": "Learning Nugget", "id": "nuggetName"},
{"name": "Learning Block", "id": "name"},
{"name": "Kurs", "id": "coursename"},
{"name": "Aufrufe", "id": "Aufrufe"},
{"name": "Schwierikeit", "id": "difficulty"},
{"name": "Typ", "id": "type"}
]
grouped_data = filtered_df.groupby(['nuggetName', 'coursename', 'name', 'type', 'difficulty']).agg(
Used_LNs=('user_id', 'nunique')
).reset_index()
columns = [
{"name": "Learning Nugget", "id": "nuggetName"},
{"name": "Learning Block", "id": "name"},
{"name": "Kurs", "id": "coursename"},
{"name": "Aufrufe", "id": "Aufrufe"},
{"name": "Schwierikeit", "id": "difficulty"},
{"name": "Typ", "id": "type"}
]
data = sum_per_LN_course.to_dict('records')
elif view == 'type':
if unique_counts:
grouped_data = filtered_df.groupby(['day', 'type'])['user_id'].nunique().unstack(fill_value=0)
else:
grouped_data = filtered_df.groupby(['day', 'type']).size().unstack(fill_value=0)
sum_per_type = grouped_data.sum(axis=0).reset_index()
avg_calls_per_day = sum_per_type[0] / len(grouped_data)
sum_per_type.columns = ['type', 'type_calls']
sum_per_type['avg_calls_per_day'] = round(avg_calls_per_day, 2)
columns = [
{"name": "Learning Nugget Typ", "id": "type"},
{"name": "Aufrufe", "id": "type_calls"},
{"name": "Durchschnittliche Aufrufe pro Tag", "id": "avg_calls_per_day"}
]
data = sum_per_type.to_dict('records')
return data, columns
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7010, debug=True)