From 04480235fbb15f4de65115c7d214ce6a547d0109 Mon Sep 17 00:00:00 2001 From: TeachMeTW Date: Fri, 11 Oct 2024 11:45:27 -0700 Subject: [PATCH 1/5] Loading components now visible --- app_sidebar_collapsible.py | 47 ++++++++++++++++++++----- pages/data.py | 2 +- pages/home.py | 72 ++++++++++++++++++++++++-------------- 3 files changed, 84 insertions(+), 37 deletions(-) diff --git a/app_sidebar_collapsible.py b/app_sidebar_collapsible.py index 71b51b7d..b734a1bc 100644 --- a/app_sidebar_collapsible.py +++ b/app_sidebar_collapsible.py @@ -241,17 +241,46 @@ def make_home_page(): return [ ] -def make_layout(): return html.Div([ - dcc.Location(id='url', refresh=False), - dcc.Store(id='store-trips', data={}), - dcc.Store(id='store-uuids', data={}), - dcc.Store(id='store-excluded-uuids', data={}), # list of UUIDs from excluded subgroups - dcc.Store(id='store-demographics', data={}), - dcc.Store(id='store-trajectories', data={}), - html.Div(id='page-content', children=make_home_page()), -]) +def make_layout(): + layout = html.Div([ + html.Div(id='home-page-load', style={'display': 'none'}), + dcc.Location(id='url', refresh=False), + dcc.Store(id='store-trips', data={}), + dcc.Store(id='store-uuids', data={}), + dcc.Store(id='store-excluded-uuids', data={}), + dcc.Store(id='store-demographics', data={}), + dcc.Store(id='store-trajectories', data={}), + html.Div(id='page-content', children=make_home_page()), + + ]) + logging.debug("Main layout component IDs: %s", get_all_component_ids(layout)) + return layout + app.layout = make_layout +def get_all_component_ids(component): + ids = [] + if hasattr(component, 'id') and component.id: + ids.append(component.id) + if hasattr(component, 'children'): + children = component.children + if isinstance(children, list): + for child in children: + ids.extend(get_all_component_ids(child)) + else: + ids.extend(get_all_component_ids(children)) + return ids + + + +app.validation_layout = html.Div([ + make_layout(), + *[page['layout'] for page in dash.page_registry.values()] +]) + +logging.debug("Validation layout component IDs: %s", get_all_component_ids(app.validation_layout)) + + # make the 'filters' menu collapsible @app.callback( Output("collapse-filters", "is_open"), diff --git a/pages/data.py b/pages/data.py index 29a5c86e..bd215745 100644 --- a/pages/data.py +++ b/pages/data.py @@ -31,7 +31,7 @@ html.Div(id='tabs-content'), dcc.Store(id='selected-tab', data='tab-uuids-datatable'), # Store to hold selected tab dcc.Interval(id='interval-load-more', interval=20000, n_intervals=0), # default loading at 10s, can be lowered or hightened based on perf (usual process local is 3s) - dcc.Store(id='store-uuids', data=[]), # Store to hold the original UUIDs data + #dcc.Store(id='store-uuids', data=[]), # Store to hold the original UUIDs data dcc.Store(id='store-loaded-uuids', data={'data': [], 'loaded': False}), # Store to track loaded data # RadioItems for key list switch, wrapped in a div that can hide/show html.Div( diff --git a/pages/home.py b/pages/home.py index c0b75f9d..420667a8 100644 --- a/pages/home.py +++ b/pages/home.py @@ -5,7 +5,7 @@ """ from uuid import UUID -from dash import dcc, html, Input, Output, callback, register_page +from dash import dcc, html, Input, Output, callback, register_page, no_update import dash_bootstrap_components as dbc import plotly.express as px @@ -35,17 +35,17 @@ [ dcc.Markdown(intro), - # Cards + # Cards with loading spinners dbc.Row([ - dbc.Col(id='card-users'), - dbc.Col(id='card-active-users'), - dbc.Col(id='card-trips') + dbc.Col(dcc.Loading(children=[html.Div(id='card-users')], type='default')), + dbc.Col(dcc.Loading(children=[html.Div(id='card-active-users')], type='default')), + dbc.Col(dcc.Loading(children=[html.Div(id='card-trips')], type='default')) ]), - # Plots + # Plots with loading spinners dbc.Row([ - dcc.Graph(id="fig-sign-up-trend"), - dcc.Graph(id="fig-trips-trend"), + dbc.Col(dcc.Loading(children=[dcc.Graph(id="fig-sign-up-trend")], type='default')), + dbc.Col(dcc.Loading(children=[dcc.Graph(id="fig-trips-trend")], type='default')), ]) ] ) @@ -117,22 +117,32 @@ def generate_card(title_text, body_text, icon): ]) return card - @callback( Output('card-users', 'children'), Input('store-uuids', 'data'), + Input('url', 'pathname'), + Input('home-page-load', 'children') ) -def update_card_users(store_uuids): - number_of_users = store_uuids.get('length') if has_permission('overview_users') else 0 +def update_card_users(store_uuids, pathname, _): + if pathname != "/": + return no_update + if store_uuids is None or not has_permission('overview_users'): + number_of_users = 0 + else: + number_of_users = store_uuids.get('length', 0) card = generate_card("# Users", f"{number_of_users} users", "fa fa-users") return card - @callback( Output('card-active-users', 'children'), Input('store-uuids', 'data'), + Input('url', 'pathname'), # Added Input + Input('home-page-load', 'children') ) -def update_card_active_users(store_uuids): +def update_card_active_users(store_uuids, pathname, _): + if pathname != "/": + return no_update + uuid_df = pd.DataFrame(store_uuids.get('data')) number_of_active_users = 0 if not uuid_df.empty and has_permission('overview_active_users'): @@ -141,49 +151,57 @@ def update_card_active_users(store_uuids): card = generate_card("# Active users", f"{number_of_active_users} users", "fa fa-person-walking") return card - @callback( Output('card-trips', 'children'), Input('store-trips', 'data'), + Input('url', 'pathname') # Added Input ) -def update_card_trips(store_trips): +def update_card_trips(store_trips, pathname): + if pathname != "/": + return no_update + number_of_trips = store_trips.get('length') if has_permission('overview_trips') else 0 card = generate_card("# Confirmed trips", f"{number_of_trips} trips", "fa fa-angles-right") return card - def generate_barplot(data, x, y, title): fig = px.bar() - if data is not None: + if data is not None and not data.empty: fig = px.bar(data, x=x, y=y) fig.update_layout(title=title) return fig - @callback( Output('fig-sign-up-trend', 'figure'), Input('store-uuids', 'data'), + Input('url', 'pathname') # Added Input ) -def generate_plot_sign_up_trend(store_uuids): +def generate_plot_sign_up_trend(store_uuids, pathname): + if pathname != "/": + return no_update + df = pd.DataFrame(store_uuids.get("data")) trend_df = None if not df.empty and has_permission('overview_signup_trends'): trend_df = compute_sign_up_trend(df) - fig = generate_barplot(trend_df, x = 'date', y = 'count', title = "Sign-ups trend") + fig = generate_barplot(trend_df, x='date', y='count', title="Sign-ups trend") return fig - @callback( Output('fig-trips-trend', 'figure'), Input('store-trips', 'data'), - Input('date-picker', 'start_date'), # these are ISO strings - Input('date-picker', 'end_date'), # these are ISO strings + Input('date-picker', 'start_date'), # these are ISO strings + Input('date-picker', 'end_date'), # these are ISO strings + Input('url', 'pathname') # Added Input ) -def generate_plot_trips_trend(store_trips, start_date, end_date): +def generate_plot_trips_trend(store_trips, start_date, end_date, pathname): + if pathname != "/": + return no_update + df = pd.DataFrame(store_trips.get("data")) trend_df = None (start_date, end_date) = iso_to_date_only(start_date, end_date) if not df.empty and has_permission('overview_trips_trend'): - trend_df = compute_trips_trend(df, date_col = "trip_start_time_str") - fig = generate_barplot(trend_df, x = 'date', y = 'count', title = f"Trips trend({start_date} to {end_date})") - return fig + trend_df = compute_trips_trend(df, date_col="trip_start_time_str") + fig = generate_barplot(trend_df, x='date', y='count', title=f"Trips trend ({start_date} to {end_date})") + return fig \ No newline at end of file From 6123afd6ef7c51880fa84e23acfc7c757d3b107f Mon Sep 17 00:00:00 2001 From: TeachMeTW Date: Tue, 15 Oct 2024 13:40:37 -0700 Subject: [PATCH 2/5] Skeleton --- app_sidebar_collapsible.py | 2 +- pages/home.py | 205 ++++++++++++++++++++++++------------- 2 files changed, 137 insertions(+), 70 deletions(-) diff --git a/app_sidebar_collapsible.py b/app_sidebar_collapsible.py index b734a1bc..947bbaac 100644 --- a/app_sidebar_collapsible.py +++ b/app_sidebar_collapsible.py @@ -414,4 +414,4 @@ def display_page(search): SESSION_COOKIE_HTTPONLY=True ) logging.debug("after override, current server config = %s" % server.config) - app.run_server(debug=envDebug, host='0.0.0.0', port=envPort) + app.run_server(debug=envDebug, host='0.0.0.0', port=envPort) \ No newline at end of file diff --git a/pages/home.py b/pages/home.py index 420667a8..2ef3460e 100644 --- a/pages/home.py +++ b/pages/home.py @@ -9,6 +9,7 @@ import dash_bootstrap_components as dbc import plotly.express as px +import dash_mantine_components as dmc # Etc import pandas as pd @@ -24,32 +25,47 @@ intro = "## Home" -card_icon = { +card_icon_style = { "color": "white", "textAlign": "center", "fontSize": 30, "margin": "auto", } -layout = html.Div( - [ - dcc.Markdown(intro), - - # Cards with loading spinners - dbc.Row([ - dbc.Col(dcc.Loading(children=[html.Div(id='card-users')], type='default')), - dbc.Col(dcc.Loading(children=[html.Div(id='card-active-users')], type='default')), - dbc.Col(dcc.Loading(children=[html.Div(id='card-trips')], type='default')) - ]), - - # Plots with loading spinners - dbc.Row([ - dbc.Col(dcc.Loading(children=[dcc.Graph(id="fig-sign-up-trend")], type='default')), - dbc.Col(dcc.Loading(children=[dcc.Graph(id="fig-trips-trend")], type='default')), - ]) - ] -) +def generate_card(title_text, body_text, icon_class): + return dbc.CardGroup([ + dbc.Card( + dbc.CardBody([ + html.H5(title_text, className="card-title"), + html.P(body_text, className="card-text"), + ]) + ), + dbc.Card( + html.I(className=icon_class, style=card_icon_style), # Font Awesome Icons + className="bg-primary", + style={"maxWidth": 75}, + ), + ]) +def generate_barplot(data, x, y, title): + if data is not None and not data.empty: + fig = px.bar(data, x=x, y=y) + else: + # Create an empty figure with a message + fig = px.bar(title=title) + fig.update_layout( + annotations=[ + dict( + text="No data available", + xref="paper", + yref="paper", + showarrow=False, + font=dict(size=20) + ) + ] + ) + fig.update_layout(title=title) + return fig def compute_sign_up_trend(uuid_df): uuid_df['update_ts'] = pd.to_datetime(uuid_df['update_ts'], utc=True) @@ -62,7 +78,6 @@ def compute_sign_up_trend(uuid_df): ) return res_df - def compute_trips_trend(trips_df, date_col): trips_df[date_col] = pd.to_datetime(trips_df[date_col], utc=True) trips_df[date_col] = pd.DatetimeIndex(trips_df[date_col]).date @@ -75,7 +90,6 @@ def compute_trips_trend(trips_df, date_col): ) return res_df - def find_last_get(uuid_list): uuid_list = [UUID(npu) for npu in uuid_list] last_item = list(edb.get_timeseries_db().aggregate([ @@ -86,7 +100,6 @@ def find_last_get(uuid_list): ])) return last_item - def get_number_of_active_users(uuid_list, threshold): last_get_entries = find_last_get(uuid_list) number_of_active_users = 0 @@ -98,110 +111,164 @@ def get_number_of_active_users(uuid_list, threshold): number_of_active_users += 1 return number_of_active_users +def wrap_with_skeleton(component_id, height, children_component): + return dmc.Skeleton( + height=height, + visible=True, + id=f'skeleton-{component_id}', + children=children_component + ) + +# Define the layout with MantineProvider +layout = dmc.MantineProvider( + theme={ + "colorScheme": "light", # or "dark" + }, + children=html.Div( + [ + dcc.Markdown(intro), -def generate_card(title_text, body_text, icon): - card = dbc.CardGroup([ - dbc.Card( - dbc.CardBody( - [ - html.H5(title_text, className="card-title"), - html.P(body_text, className="card-text",), - ] - ) + # Cards Section + dbc.Row([ + dbc.Col( + wrap_with_skeleton('users', 100, html.Div(id='card-users')) ), - dbc.Card( - html.Div(className=icon, style=card_icon), - className="bg-primary", - style={"maxWidth": 75}, + dbc.Col( + wrap_with_skeleton('active-users', 100, html.Div(id='card-active-users')) ), - ]) - return card + dbc.Col( + wrap_with_skeleton('trips', 100, html.Div(id='card-trips')) + ), + ], className="mb-4"), # Add margin-bottom for spacing + + # Plots Section + dbc.Row([ + dbc.Col( + wrap_with_skeleton('sign-up-trend', 300, dcc.Graph(id="fig-sign-up-trend")) + ), + dbc.Col( + wrap_with_skeleton('trips-trend', 300, dcc.Graph(id="fig-trips-trend")) + ), + ]), + ], + style={"padding": "20px"} # Optional padding for aesthetics + ) +) +# Callbacks to update Users Card @callback( Output('card-users', 'children'), + Output('skeleton-users', 'visible'), Input('store-uuids', 'data'), Input('url', 'pathname'), Input('home-page-load', 'children') ) def update_card_users(store_uuids, pathname, _): if pathname != "/": - return no_update + return no_update, no_update + if store_uuids is None or not has_permission('overview_users'): number_of_users = 0 else: number_of_users = store_uuids.get('length', 0) + card = generate_card("# Users", f"{number_of_users} users", "fa fa-users") - return card + return card, False # Hide the skeleton +# Callbacks to update Active Users Card @callback( Output('card-active-users', 'children'), + Output('skeleton-active-users', 'visible'), Input('store-uuids', 'data'), - Input('url', 'pathname'), # Added Input + Input('url', 'pathname'), Input('home-page-load', 'children') ) def update_card_active_users(store_uuids, pathname, _): if pathname != "/": - return no_update + return no_update, no_update + + if store_uuids is None: + uuid_df = pd.DataFrame() + else: + uuid_data = store_uuids.get('data', []) + uuid_df = pd.DataFrame(uuid_data) if isinstance(uuid_data, list) else pd.DataFrame() - uuid_df = pd.DataFrame(store_uuids.get('data')) number_of_active_users = 0 if not uuid_df.empty and has_permission('overview_active_users'): - one_day = 24 * 60 * 60 + one_day = 24 * 60 * 60 # Threshold in seconds number_of_active_users = get_number_of_active_users(uuid_df['user_id'], one_day) - card = generate_card("# Active users", f"{number_of_active_users} users", "fa fa-person-walking") - return card + card = generate_card("# Active Users", f"{number_of_active_users} users", "fa fa-person-walking") + return card, False # Hide the skeleton + +# Callbacks to update Trips Card @callback( Output('card-trips', 'children'), + Output('skeleton-trips', 'visible'), Input('store-trips', 'data'), - Input('url', 'pathname') # Added Input + Input('url', 'pathname') ) def update_card_trips(store_trips, pathname): if pathname != "/": - return no_update + return no_update, no_update - number_of_trips = store_trips.get('length') if has_permission('overview_trips') else 0 - card = generate_card("# Confirmed trips", f"{number_of_trips} trips", "fa fa-angles-right") - return card + if store_trips is None or not has_permission('overview_trips'): + number_of_trips = 0 + else: + number_of_trips = store_trips.get('length', 0) -def generate_barplot(data, x, y, title): - fig = px.bar() - if data is not None and not data.empty: - fig = px.bar(data, x=x, y=y) - fig.update_layout(title=title) - return fig + card = generate_card("# Confirmed Trips", f"{number_of_trips} trips", "fa fa-angles-right") + return card, False # Hide the skeleton +# Callbacks to update Sign-Up Trend Graph @callback( Output('fig-sign-up-trend', 'figure'), + Output('skeleton-sign-up-trend', 'visible'), Input('store-uuids', 'data'), - Input('url', 'pathname') # Added Input + Input('url', 'pathname') ) def generate_plot_sign_up_trend(store_uuids, pathname): if pathname != "/": - return no_update + return no_update, no_update + + if store_uuids is None: + df = pd.DataFrame() + else: + uuid_data = store_uuids.get("data", []) + df = pd.DataFrame(uuid_data) if isinstance(uuid_data, list) else pd.DataFrame() - df = pd.DataFrame(store_uuids.get("data")) trend_df = None if not df.empty and has_permission('overview_signup_trends'): trend_df = compute_sign_up_trend(df) - fig = generate_barplot(trend_df, x='date', y='count', title="Sign-ups trend") - return fig + fig = generate_barplot(trend_df, x='date', y='count', title="Sign-ups Trend") + return fig, False # Hide the skeleton + +# Callbacks to update Trips Trend Graph @callback( Output('fig-trips-trend', 'figure'), + Output('skeleton-trips-trend', 'visible'), Input('store-trips', 'data'), - Input('date-picker', 'start_date'), # these are ISO strings - Input('date-picker', 'end_date'), # these are ISO strings - Input('url', 'pathname') # Added Input + Input('date-picker', 'start_date'), + Input('date-picker', 'end_date'), + Input('url', 'pathname') ) def generate_plot_trips_trend(store_trips, start_date, end_date, pathname): if pathname != "/": - return no_update + return no_update, no_update + + if store_trips is None: + df = pd.DataFrame() + else: + trips_data = store_trips.get("data", []) + df = pd.DataFrame(trips_data) if isinstance(trips_data, list) else pd.DataFrame() - df = pd.DataFrame(store_trips.get("data")) trend_df = None - (start_date, end_date) = iso_to_date_only(start_date, end_date) + if start_date and end_date: + start_date, end_date = iso_to_date_only(start_date, end_date) + if not df.empty and has_permission('overview_trips_trend'): trend_df = compute_trips_trend(df, date_col="trip_start_time_str") - fig = generate_barplot(trend_df, x='date', y='count', title=f"Trips trend ({start_date} to {end_date})") - return fig \ No newline at end of file + + fig = generate_barplot(trend_df, x='date', y='count', title=f"Trips Trend ({start_date} to {end_date})") + return fig, False # Hide the skeleton \ No newline at end of file From 70c9eb7785024911de99af5d94cb01327842626e Mon Sep 17 00:00:00 2001 From: TeachMeTW Date: Tue, 15 Oct 2024 14:17:07 -0700 Subject: [PATCH 3/5] Removed whitespace changes --- pages/home.py | 203 ++++++++++++++++++++------------------------------ 1 file changed, 80 insertions(+), 123 deletions(-) diff --git a/pages/home.py b/pages/home.py index 2ef3460e..d9711a2e 100644 --- a/pages/home.py +++ b/pages/home.py @@ -7,9 +7,9 @@ from uuid import UUID from dash import dcc, html, Input, Output, callback, register_page, no_update import dash_bootstrap_components as dbc +import dash_mantine_components as dmc import plotly.express as px -import dash_mantine_components as dmc # Etc import pandas as pd @@ -25,48 +25,56 @@ intro = "## Home" -card_icon_style = { +card_icon = { "color": "white", "textAlign": "center", "fontSize": 30, "margin": "auto", } -def generate_card(title_text, body_text, icon_class): - return dbc.CardGroup([ - dbc.Card( - dbc.CardBody([ - html.H5(title_text, className="card-title"), - html.P(body_text, className="card-text"), - ]) - ), - dbc.Card( - html.I(className=icon_class, style=card_icon_style), # Font Awesome Icons - className="bg-primary", - style={"maxWidth": 75}, - ), - ]) +def wrap_with_skeleton(component_id, height, children_component): + return dmc.Skeleton( + height=height, + visible=True, + id=f'skeleton-{component_id}', + children=children_component + ) -def generate_barplot(data, x, y, title): - if data is not None and not data.empty: - fig = px.bar(data, x=x, y=y) - else: - # Create an empty figure with a message - fig = px.bar(title=title) - fig.update_layout( - annotations=[ - dict( - text="No data available", - xref="paper", - yref="paper", - showarrow=False, - font=dict(size=20) - ) - ] - ) - fig.update_layout(title=title) - return fig +layout = layout = dmc.MantineProvider( + theme={ + "colorScheme": "light", # or "dark" + }, + + children=html.Div( + [ + dcc.Markdown(intro), + # Cards Section + dbc.Row([ + dbc.Col( + wrap_with_skeleton('users', 100, html.Div(id='card-users')) + ), + dbc.Col( + wrap_with_skeleton('active-users', 100, html.Div(id='card-active-users')) + ), + dbc.Col( + wrap_with_skeleton('trips', 100, html.Div(id='card-trips')) + ), + ], className="mb-4"), # Add margin-bottom for spacing + + # Plots Section + dbc.Row([ + dbc.Col( + wrap_with_skeleton('sign-up-trend', 300, dcc.Graph(id="fig-sign-up-trend")) + ), + dbc.Col( + wrap_with_skeleton('trips-trend', 300, dcc.Graph(id="fig-trips-trend")) + ), + ]), + ], + style={"padding": "20px"} # Optional padding for aesthetics + ) +) def compute_sign_up_trend(uuid_df): uuid_df['update_ts'] = pd.to_datetime(uuid_df['update_ts'], utc=True) res_df = ( @@ -111,51 +119,23 @@ def get_number_of_active_users(uuid_list, threshold): number_of_active_users += 1 return number_of_active_users -def wrap_with_skeleton(component_id, height, children_component): - return dmc.Skeleton( - height=height, - visible=True, - id=f'skeleton-{component_id}', - children=children_component - ) - -# Define the layout with MantineProvider -layout = dmc.MantineProvider( - theme={ - "colorScheme": "light", # or "dark" - }, - children=html.Div( - [ - dcc.Markdown(intro), - - # Cards Section - dbc.Row([ - dbc.Col( - wrap_with_skeleton('users', 100, html.Div(id='card-users')) - ), - dbc.Col( - wrap_with_skeleton('active-users', 100, html.Div(id='card-active-users')) - ), - dbc.Col( - wrap_with_skeleton('trips', 100, html.Div(id='card-trips')) - ), - ], className="mb-4"), # Add margin-bottom for spacing - - # Plots Section - dbc.Row([ - dbc.Col( - wrap_with_skeleton('sign-up-trend', 300, dcc.Graph(id="fig-sign-up-trend")) +def generate_card(title_text, body_text, icon): + card = dbc.CardGroup([ + dbc.Card( + dbc.CardBody( + [ + html.H5(title_text, className="card-title"), + html.P(body_text, className="card-text",), + ] + ) ), - dbc.Col( - wrap_with_skeleton('trips-trend', 300, dcc.Graph(id="fig-trips-trend")) + dbc.Card( + html.Div(className=icon, style=card_icon), + className="bg-primary", + style={"maxWidth": 75}, ), - ]), - ], - style={"padding": "20px"} # Optional padding for aesthetics - ) -) - -# Callbacks to update Users Card + ]) + return card @callback( Output('card-users', 'children'), Output('skeleton-users', 'visible'), @@ -173,9 +153,8 @@ def update_card_users(store_uuids, pathname, _): number_of_users = store_uuids.get('length', 0) card = generate_card("# Users", f"{number_of_users} users", "fa fa-users") - return card, False # Hide the skeleton - -# Callbacks to update Active Users Card + # Hide the skeleton by setting visible to False + return card, False @callback( Output('card-active-users', 'children'), Output('skeleton-active-users', 'visible'), @@ -187,21 +166,13 @@ def update_card_active_users(store_uuids, pathname, _): if pathname != "/": return no_update, no_update - if store_uuids is None: - uuid_df = pd.DataFrame() - else: - uuid_data = store_uuids.get('data', []) - uuid_df = pd.DataFrame(uuid_data) if isinstance(uuid_data, list) else pd.DataFrame() - + uuid_df = pd.DataFrame(store_uuids.get('data')) if store_uuids else pd.DataFrame() number_of_active_users = 0 if not uuid_df.empty and has_permission('overview_active_users'): - one_day = 24 * 60 * 60 # Threshold in seconds + one_day = 24 * 60 * 60 number_of_active_users = get_number_of_active_users(uuid_df['user_id'], one_day) - - card = generate_card("# Active Users", f"{number_of_active_users} users", "fa fa-person-walking") - return card, False # Hide the skeleton - -# Callbacks to update Trips Card + card = generate_card("# Active users", f"{number_of_active_users} users", "fa fa-person-walking") + return card, False @callback( Output('card-trips', 'children'), Output('skeleton-trips', 'visible'), @@ -212,15 +183,16 @@ def update_card_trips(store_trips, pathname): if pathname != "/": return no_update, no_update - if store_trips is None or not has_permission('overview_trips'): - number_of_trips = 0 - else: - number_of_trips = store_trips.get('length', 0) - - card = generate_card("# Confirmed Trips", f"{number_of_trips} trips", "fa fa-angles-right") - return card, False # Hide the skeleton + number_of_trips = store_trips.get('length') if (store_trips and has_permission('overview_trips')) else 0 + card = generate_card("# Confirmed trips", f"{number_of_trips} trips", "fa fa-angles-right") + return card, False -# Callbacks to update Sign-Up Trend Graph +def generate_barplot(data, x, y, title): + fig = px.bar() + if data is not None and not data.empty: + fig = px.bar(data, x=x, y=y) + fig.update_layout(title=title) + return fig @callback( Output('fig-sign-up-trend', 'figure'), Output('skeleton-sign-up-trend', 'visible'), @@ -230,19 +202,12 @@ def update_card_trips(store_trips, pathname): def generate_plot_sign_up_trend(store_uuids, pathname): if pathname != "/": return no_update, no_update - - if store_uuids is None: - df = pd.DataFrame() - else: - uuid_data = store_uuids.get("data", []) - df = pd.DataFrame(uuid_data) if isinstance(uuid_data, list) else pd.DataFrame() - + df = pd.DataFrame(store_uuids.get("data")) if store_uuids else pd.DataFrame() trend_df = None if not df.empty and has_permission('overview_signup_trends'): trend_df = compute_sign_up_trend(df) - - fig = generate_barplot(trend_df, x='date', y='count', title="Sign-ups Trend") - return fig, False # Hide the skeleton + fig = generate_barplot(trend_df, x='date', y='count', title="Sign-ups trend") + return fig, False # Callbacks to update Trips Trend Graph @callback( @@ -257,18 +222,10 @@ def generate_plot_trips_trend(store_trips, start_date, end_date, pathname): if pathname != "/": return no_update, no_update - if store_trips is None: - df = pd.DataFrame() - else: - trips_data = store_trips.get("data", []) - df = pd.DataFrame(trips_data) if isinstance(trips_data, list) else pd.DataFrame() - + df = pd.DataFrame(store_trips.get("data")) if store_trips else pd.DataFrame() trend_df = None - if start_date and end_date: - start_date, end_date = iso_to_date_only(start_date, end_date) - + (start_date, end_date) = iso_to_date_only(start_date, end_date) if not df.empty and has_permission('overview_trips_trend'): trend_df = compute_trips_trend(df, date_col="trip_start_time_str") - - fig = generate_barplot(trend_df, x='date', y='count', title=f"Trips Trend ({start_date} to {end_date})") - return fig, False # Hide the skeleton \ No newline at end of file + fig = generate_barplot(trend_df, x='date', y='count', title=f"Trips trend ({start_date} to {end_date})") + return fig, False \ No newline at end of file From ead5d73014ec9c1b42a4f124ae7fdd3e152df8fb Mon Sep 17 00:00:00 2001 From: TeachMeTW Date: Tue, 15 Oct 2024 14:47:06 -0700 Subject: [PATCH 4/5] Wait --- pages/home.py | 162 ++++++++++++++++++++++++++------------------------ 1 file changed, 83 insertions(+), 79 deletions(-) diff --git a/pages/home.py b/pages/home.py index d9711a2e..e68f5788 100644 --- a/pages/home.py +++ b/pages/home.py @@ -1,9 +1,3 @@ -""" -Note that the callback will trigger even if prevent_initial_call=True. This is because dcc.Location must -be in app.py. Since the dcc.Location component is not in the layout when navigating to this page, it triggers the callback. -The workaround is to check if the input value is None. - -""" from uuid import UUID from dash import dcc, html, Input, Output, callback, register_page, no_update import dash_bootstrap_components as dbc @@ -35,12 +29,12 @@ def wrap_with_skeleton(component_id, height, children_component): return dmc.Skeleton( height=height, - visible=True, + visible=True, # Initially visible id=f'skeleton-{component_id}', children=children_component ) -layout = layout = dmc.MantineProvider( +layout = dmc.MantineProvider( theme={ "colorScheme": "light", # or "dark" }, @@ -75,6 +69,7 @@ def wrap_with_skeleton(component_id, height, children_component): style={"padding": "20px"} # Optional padding for aesthetics ) ) + def compute_sign_up_trend(uuid_df): uuid_df['update_ts'] = pd.to_datetime(uuid_df['update_ts'], utc=True) res_df = ( @@ -136,96 +131,105 @@ def generate_card(title_text, body_text, icon): ), ]) return card + +def generate_barplot(data, x, y, title): + fig = px.bar() + if data is not None and not data.empty: + fig = px.bar(data, x=x, y=y) + fig.update_layout(title=title) + return fig + @callback( - Output('card-users', 'children'), - Output('skeleton-users', 'visible'), - Input('store-uuids', 'data'), - Input('url', 'pathname'), - Input('home-page-load', 'children') + [ + Output('card-users', 'children'), + Output('skeleton-users', 'visible'), + Output('card-active-users', 'children'), + Output('skeleton-active-users', 'visible'), + Output('card-trips', 'children'), + Output('skeleton-trips', 'visible'), + Output('fig-sign-up-trend', 'figure'), + Output('skeleton-sign-up-trend', 'visible'), + Output('fig-trips-trend', 'figure'), + Output('skeleton-trips-trend', 'visible'), + ], + [ + Input('store-uuids', 'data'), + Input('store-trips', 'data'), + Input('date-picker', 'start_date'), + Input('date-picker', 'end_date'), + Input('url', 'pathname'), + Input('home-page-load', 'children') + ] ) -def update_card_users(store_uuids, pathname, _): +def update_all_components(store_uuids, store_trips, start_date, end_date, pathname, _): if pathname != "/": - return no_update, no_update - + return [no_update] * 10 + + # Initialize all outputs + card_users = no_update + skeleton_users = no_update + card_active_users = no_update + skeleton_active_users = no_update + card_trips = no_update + skeleton_trips = no_update + fig_sign_up_trend = no_update + skeleton_sign_up_trend = no_update + fig_trips_trend = no_update + skeleton_trips_trend = no_update + + # Flags to check if each component's data is ready + all_ready = True + + # Update Users Card if store_uuids is None or not has_permission('overview_users'): number_of_users = 0 else: number_of_users = store_uuids.get('length', 0) + card_users = generate_card("# Users", f"{number_of_users} users", "fa fa-users") - card = generate_card("# Users", f"{number_of_users} users", "fa fa-users") - # Hide the skeleton by setting visible to False - return card, False -@callback( - Output('card-active-users', 'children'), - Output('skeleton-active-users', 'visible'), - Input('store-uuids', 'data'), - Input('url', 'pathname'), - Input('home-page-load', 'children') -) -def update_card_active_users(store_uuids, pathname, _): - if pathname != "/": - return no_update, no_update - + # Update Active Users Card uuid_df = pd.DataFrame(store_uuids.get('data')) if store_uuids else pd.DataFrame() number_of_active_users = 0 if not uuid_df.empty and has_permission('overview_active_users'): one_day = 24 * 60 * 60 number_of_active_users = get_number_of_active_users(uuid_df['user_id'], one_day) - card = generate_card("# Active users", f"{number_of_active_users} users", "fa fa-person-walking") - return card, False -@callback( - Output('card-trips', 'children'), - Output('skeleton-trips', 'visible'), - Input('store-trips', 'data'), - Input('url', 'pathname') -) -def update_card_trips(store_trips, pathname): - if pathname != "/": - return no_update, no_update + card_active_users = generate_card("# Active users", f"{number_of_active_users} users", "fa fa-person-walking") + # Update Trips Card number_of_trips = store_trips.get('length') if (store_trips and has_permission('overview_trips')) else 0 - card = generate_card("# Confirmed trips", f"{number_of_trips} trips", "fa fa-angles-right") - return card, False + card_trips = generate_card("# Confirmed trips", f"{number_of_trips} trips", "fa fa-angles-right") -def generate_barplot(data, x, y, title): - fig = px.bar() - if data is not None and not data.empty: - fig = px.bar(data, x=x, y=y) - fig.update_layout(title=title) - return fig -@callback( - Output('fig-sign-up-trend', 'figure'), - Output('skeleton-sign-up-trend', 'visible'), - Input('store-uuids', 'data'), - Input('url', 'pathname') -) -def generate_plot_sign_up_trend(store_uuids, pathname): - if pathname != "/": - return no_update, no_update + # Update Sign-Up Trend Figure df = pd.DataFrame(store_uuids.get("data")) if store_uuids else pd.DataFrame() trend_df = None if not df.empty and has_permission('overview_signup_trends'): trend_df = compute_sign_up_trend(df) - fig = generate_barplot(trend_df, x='date', y='count', title="Sign-ups trend") - return fig, False - -# Callbacks to update Trips Trend Graph -@callback( - Output('fig-trips-trend', 'figure'), - Output('skeleton-trips-trend', 'visible'), - Input('store-trips', 'data'), - Input('date-picker', 'start_date'), - Input('date-picker', 'end_date'), - Input('url', 'pathname') -) -def generate_plot_trips_trend(store_trips, start_date, end_date, pathname): - if pathname != "/": - return no_update, no_update + fig_sign_up_trend = generate_barplot(trend_df, x='date', y='count', title="Sign-ups trend") - df = pd.DataFrame(store_trips.get("data")) if store_trips else pd.DataFrame() - trend_df = None + # Update Trips Trend Figure + trips_df = pd.DataFrame(store_trips.get("data")) if store_trips else pd.DataFrame() + trend_trips_df = None (start_date, end_date) = iso_to_date_only(start_date, end_date) - if not df.empty and has_permission('overview_trips_trend'): - trend_df = compute_trips_trend(df, date_col="trip_start_time_str") - fig = generate_barplot(trend_df, x='date', y='count', title=f"Trips trend ({start_date} to {end_date})") - return fig, False \ No newline at end of file + if not trips_df.empty and has_permission('overview_trips_trend'): + trend_trips_df = compute_trips_trend(trips_df, date_col="trip_start_time_str") + fig_trips_trend = generate_barplot(trend_trips_df, x='date', y='count', title=f"Trips trend ({start_date} to {end_date})") + + # Set all skeletons to False since all components are updated + skeleton_users = False + skeleton_active_users = False + skeleton_trips = False + skeleton_sign_up_trend = False + skeleton_trips_trend = False + + return [ + card_users, + skeleton_users, + card_active_users, + skeleton_active_users, + card_trips, + skeleton_trips, + fig_sign_up_trend, + skeleton_sign_up_trend, + fig_trips_trend, + skeleton_trips_trend, + ] From 379cf0276cbe5af1923ce05258796ee23731936e Mon Sep 17 00:00:00 2001 From: TeachMeTW Date: Thu, 17 Oct 2024 17:10:14 -0700 Subject: [PATCH 5/5] removed blur --- app_sidebar_collapsible.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app_sidebar_collapsible.py b/app_sidebar_collapsible.py index 947bbaac..b706719a 100644 --- a/app_sidebar_collapsible.py +++ b/app_sidebar_collapsible.py @@ -215,7 +215,7 @@ def make_controls(): id='global-loading', type='default', fullscreen=True, - overlay_style={"visibility": "visible", "filter": "blur(2px)"}, + overlay_style={"visibility": "visible", "opacity": 0.5}, style={"background-color": "transparent"}, children=html.Div(dash.page_container, style={ "margin-left": "5rem",