Let me know if this helps. I’ll remove some of the unnecessary stuff, but it’s still a lot.
Here is my app.py. On this page I run a callback that calls to the sql database and creates a dataframe of information and stores it in the dcc.Store with id=‘store’. It has gathered information from user input from the home page. (The second dcc.Store is for something else).
app.py
import dash
import dash_bootstrap_components as dbc
from dash import html, dcc, Output, Input, State
import dash_loading_spinners as dls
from src import sql_connections as sqlc
from src import sqlite_functions as sqlf
from src import wrangler as wr
app = dash.Dash(__name__, use_pages=True,
external_stylesheets=[dbc.themes.BOOTSTRAP],
meta_tags=[{"name": "viewport", "content": "width=device-width"}],
suppress_callback_exceptions=True,
background_callback_manager=background_callback_manager
)
app.layout = html.Div([
dcc.Store(id="store", data={}, storage_type='session'),
dcc.Store(id='store-results', data={}, storage_type='session'),
dash.page_container,
]
)
@app.callback(
Output('store', 'data'),
Output('error-alert', 'is_open'),
Output('graph-redirect', 'pathname'),
Output('timeout-alert', 'is_open'),
Input('data-button', 'n_clicks'),
State('engine-drop', 'value'),
State('start-date', 'value'),
State('region-drop', 'value'),
State('report-options', 'value'),
State('loading', 'children'),
prevent_initial_call=True,
background=True,
running=[
(Output('data-button', 'disabled'), True, False),
# (Output('loading', 'visibility'), True, False),
(Output('loading', 'children'), dls.Scale(), ''),
],
)
def query_data(n1, engine, date, region, report, loading):
if loading is None:
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
if n1:
if engine == None or region == None:
store = {}
return store, True, dash.no_update, False
else:
try:
data = sqlc.get_data_for_app(region=region, engine=engine, end_date=date)
data = wr.get_past_year(df=data, end_date=date)
logging.info('Saving for Graph')
store = {"df": data.to_dict('records'), "date": date, "engine": engine, "report": report}
logging.info('Data Saved to Store and Directed to Graph Page')
return store, False, '/graph', False
except Exception:
return dash.no_update, False, dash.no_update, True
return dash.no_update, dash.no_update, dash.no_update, dash.no_update
if __name__ == "__main__":
app.run_server(
debug=True,
dev_tools_props_check=False,
)
Here is my graph.py. On this page, I take the stored data, manipulate it to create a graph. Part of that is adding lines to the graph. If lines already exist in the dataframe, it automatically adds them. The user can also add more lines. They should also be able to delete the existing lines if they want. A coworker noticed that if they delete an existing line, but then go to add a different line, the existing line repopulates. And it’s because it’s in the callback. I was trying to see if there was a way to not have that part refresh every time they go to add a line.
graph.py
from dash import html, register_page, callback, Input, Output, State, dash_table, dcc
import dash_bootstrap_components as dbc
import dash_mantine_components as dmc
from datetime import datetime
import pandas as pd
import dash
import plotly.express as px
import plotly.graph_objects as go
from scipy import signal
from dateutil import relativedelta
import numpy as np
from src import pod_sql as pod
from src import wrangler as wr
register_page(__name__)
layout = html.Div(
...
# Graph area
html.Div(
className="div-app",
id='div-app',
loading_state={'is_loading': True},
children=[
dash_loading_spinners.Pacman(
fullscreen=True,
id='loading-whole-app'
)]),
...
# Add Event Lines
dbc.Col([
html.H5('Add Event Lines'),
dcc.Dropdown(
id='event-drop',
options=['ControlsUpgrade', 'EquipmentUpgrade', 'FilterChange', 'HGP', 'MA', 'ModelUpdate', 'OfflineWash', 'Tuning'],
placeholder='Event',
),
html.P('on'),
dmc.DatePicker(
id='event-date',
maxDate=datetime.now().strftime('%Y-%m-%d'),
firstDayOfWeek='sunday',
inputFormat="MM/DD/YYYY"
),
dbc.Button(
'Add',
id='add-lines-btn',
color='secondary',
n_clicks=0,
)
]),
#Event table
dbc.Col([
html.Center(html.H5('Event Lines')),
dash_table.DataTable(
id='selections',
columns=[
{'name': 'selected', 'id': 'selected'},
],
data=[],
editable=True,
row_selectable=True
),
dbc.Button(
'Remove',
id='remove-event-btn',
n_clicks=0
)
])
]),
])
]
)
# # Event Lines
@callback(
Output('selections', 'data'),
Output('event-drop', 'value'),
Output('event-date', 'value'),
State('event-drop', 'value'),
State('event-date', 'value'),
State('selections', 'data'),
Input('add-lines-btn', 'n_clicks'),
Input('store', 'data'),
Input('remove-event-btn', 'n_clicks')
)
def update_event_table(event_drop, event_date, selections, add_btn, data, rm_btn):
df = pod.get_prev_events_for_current_graph(report=data['report'], engine_name=data['engine'])
if type(df) == pd.DataFrame:
if not df.empty:
logging.info(f'Importing {len(df.index)} events from previous report')
i = 0
while i < len(df.index):
if selections:
selections.append({'selected': df['Event'][i]})
i += 1
else:
selections = [{'selected': df['Event'][i]}]
i += 1
if event_drop:
if selections:
selections.append({'selected': f'{event_date} {event_drop}'})
selections_unique = []
[selections_unique.append(x) for x in selections if x not in selections_unique]
return selections_unique, dash.no_update, dash.no_update
else:
if event_drop:
if selections:
logging.info(f'Adding event {event_date} {event_drop}')
selections.append({'selected': f'{event_date} {event_drop}'})
else:
logging.info(f'Adding event {event_date} {event_drop}')
selections = [{'selected': f'{event_date} {event_drop}'}]
return selections, dash.no_update, dash.no_update
# Callback for graph
@callback(
Output('div-app', 'children'),
Output('data-alert', 'is_open'),
Output('offset-alert', 'is_open'),
Input('store', 'data'),
Input('new-start-date', 'value'),
State('data-alert', 'is_open'),
Input('offset-checkbox', 'value'),
Input('offset-number', 'value'),
State('offset-alert', 'is_open'),
Input('lower-bound', 'value'),
Input('upper-bound', 'value'),
Input('selections', 'data')
)
def create_graph(
data,
new_start_date,
open_alert,
offset_initial,
offset_custom,
offset_alert,
lower_bound,
upper_bound,
events
):
...
logging.debug(f'{type(events)}')
if events:
i = 0
while i < len(events):
event_date = events[i]['selected'][:10]
event_type = events[i]['selected'][11:]
logging.info(f'Adding event: {event_date} {event_type}')
location = df['MeasureDate'].searchsorted(f'{event_date}T00:00:00')
if event_type == 'ControlsUpgrade':
fig3.add_vline(x=location, line_color='rgb(255, 127, 14)', line_width=2)
i += 1
if event_type == 'EquipmentUpgrade':
fig3.add_vline(x=location, line_color='rgb(44, 160, 44)', line_width=2)
i += 1
if event_type == 'FilterChange':
fig3.add_vline(x=location, line_color='rgb(214, 39, 40)', line_width=2)
i += 1
if event_type == 'HGP':
fig3.add_vline(x=location, line_color='rgb(148, 103, 189)', line_width=2)
i += 1
if event_type == 'MA':
fig3.add_vline(x=location, line_color='rgb(227, 119, 194)', line_width=2)
i += 1
if event_type == 'ModelUpdate':
fig3.add_vline(x=location, line_color='rgb(188, 189, 34)', line_width=2)
i += 1
if event_type == 'OfflineWash':
fig3.add_vline(x=location, line_color='rgb(23, 190, 207)', line_width=2)
i += 1
if event_type == 'Tuning':
fig3.add_vline(x=location, line_color='rgb(31, 119, 180)', line_width=2)
i += 1
return dcc.Graph(figure=fig3), open_alert, open_alert