diff --git a/pages/data.py b/pages/data.py index 29a5c86..2b774bb 100644 --- a/pages/data.py +++ b/pages/data.py @@ -22,17 +22,22 @@ layout = html.Div( [ dcc.Markdown(intro), - dcc.Tabs(id="tabs-datatable", value='tab-uuids-datatable', children=[ - dcc.Tab(label='UUIDs', value='tab-uuids-datatable'), - dcc.Tab(label='Trips', value='tab-trips-datatable'), - dcc.Tab(label='Demographics', value='tab-demographics-datatable'), - dcc.Tab(label='Trajectories', value='tab-trajectories-datatable'), - ]), + dcc.Tabs( + id="tabs-datatable", + value='tab-uuids-datatable', + children=[ + dcc.Tab(label='UUIDs', value='tab-uuids-datatable'), + dcc.Tab(label='Trips', value='tab-trips-datatable'), + dcc.Tab(label='Demographics', value='tab-demographics-datatable'), + dcc.Tab(label='Trajectories', value='tab-trajectories-datatable'), + ] + ), 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.Interval(id='interval-load-more', interval=24000, n_intervals=0), # Interval for loading more 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 + dcc.Store(id='uuids-page-current', data=0), # Store to track current page for UUIDs DataTable # RadioItems for key list switch, wrapped in a div that can hide/show html.Div( id='keylist-switch-container', @@ -54,7 +59,6 @@ ) - def clean_location_data(df): if 'data.start_loc.coordinates' in df.columns: df['data.start_loc.coordinates'] = df['data.start_loc.coordinates'].apply(lambda x: f'({x[0]}, {x[1]})') @@ -83,6 +87,17 @@ def show_keylist_switch(tab): return {'display': 'none'} # Hide the keylist-switch on all other tabs +@callback( + Output('uuids-page-current', 'data'), + Input('uuid-table', 'page_current'), + State('tabs-datatable', 'value') +) +def update_uuids_page_current(page_current, selected_tab): + if selected_tab == 'tab-uuids-datatable': + return page_current + raise PreventUpdate + + @callback( Output('tabs-content', 'children'), Output('store-loaded-uuids', 'data'), @@ -98,16 +113,20 @@ def show_keylist_switch(tab): Input('date-picker-timezone', 'value'), Input('interval-load-more', 'n_intervals'), # Interval to trigger the loading of more data Input('keylist-switch', 'value'), # Add keylist-switch to trigger data refresh on change + Input('uuids-page-current', 'data'), # Current page number for UUIDs DataTable State('store-loaded-uuids', 'data'), # Use State to track already loaded data State('store-loaded-uuids', 'loaded') # Keep track if we have finished loading all data ) -def render_content(tab, store_uuids, store_excluded_uuids, store_trips, store_demographics, store_trajectories, - start_date, end_date, timezone, n_intervals, key_list, loaded_uuids_store, all_data_loaded): +def render_content( + tab, store_uuids, store_excluded_uuids, store_trips, store_demographics, store_trajectories, + start_date, end_date, timezone, n_intervals, key_list, current_page, + loaded_uuids_store, all_data_loaded +): initial_batch_size = 10 # Define the batch size for loading UUIDs # Update selected tab selected_tab = tab - logging.debug(f"Selected tab: {selected_tab}") + # Handle the UUIDs tab without fullscreen loading spinner if tab == 'tab-uuids-datatable': # Ensure store_uuids contains the key 'data' which is a list of dictionaries @@ -115,7 +134,6 @@ def render_content(tab, store_uuids, store_excluded_uuids, store_trips, store_de logging.error(f"Expected store_uuids to be a dict with a 'data' key, but got {type(store_uuids)}") return html.Div([html.P("Data structure error.")]), loaded_uuids_store, True - # Extract the list of UUIDs from the dict uuids_list = store_uuids['data'] # Ensure uuids_list is a list for slicing @@ -123,7 +141,6 @@ def render_content(tab, store_uuids, store_excluded_uuids, store_trips, store_de logging.error(f"Expected store_uuids['data'] to be a list but got {type(uuids_list)}") return html.Div([html.P("Data structure error.")]), loaded_uuids_store, True - # Retrieve already loaded data from the store loaded_data = loaded_uuids_store.get('data', []) total_loaded = len(loaded_data) @@ -131,36 +148,27 @@ def render_content(tab, store_uuids, store_excluded_uuids, store_trips, store_de if not loaded_uuids_store.get('loaded', False): total_to_load = total_loaded + initial_batch_size total_to_load = min(total_to_load, len(uuids_list)) # Avoid loading more than available - - logging.debug(f"Loading next batch of UUIDs: {total_loaded} to {total_to_load}") - - # Slice the list of UUIDs from the dict new_data = uuids_list[total_loaded:total_to_load] if new_data: # Process and append the new data to the loaded store processed_data = db_utils.add_user_stats(new_data, initial_batch_size) loaded_data.extend(processed_data) - # Update the store with the new data - loaded_uuids_store['data'] = loaded_data - loaded_uuids_store['loaded'] = len(loaded_data) >= len(uuids_list) # Mark all data as loaded if done - - logging.debug(f"New batch loaded. Total loaded: {len(loaded_data)}") + loaded_uuids_store['data'] = loaded_data # Mark all data as loaded if done + loaded_uuids_store['loaded'] = len(loaded_data) >= len(uuids_list) # Prepare the data to be displayed columns = perm_utils.get_uuids_columns() # Get the relevant columns df = pd.DataFrame(loaded_data) if df.empty or not perm_utils.has_permission('data_uuids'): - logging.debug("No data or permission issues.") return html.Div([html.P("No data available or you don't have permission.")]), loaded_uuids_store, True df = df.drop(columns=[col for col in df.columns if col not in columns]) - logging.debug("Returning appended data to update the UI.") content = html.Div([ - populate_datatable(df), + populate_datatable(df, table_id='uuid-table', page_current=current_page), html.P( f"Showing {len(loaded_data)} of {len(uuids_list)} UUIDs." + (f" Loading 10 more..." if not loaded_uuids_store.get('loaded', False) else ""), @@ -184,8 +192,8 @@ def render_content(tab, store_uuids, store_excluded_uuids, store_trips, store_de df = df.drop(columns=[col for col in df.columns if col not in columns]) df = clean_location_data(df) - trips_table = populate_datatable(df, 'trips-table') - logging.debug(f"Returning 3 values: {trips_table}, {loaded_uuids_store}, True") + trips_table = populate_datatable(df) + return html.Div([ html.Button('Display columns with raw units', id='button-clicked', n_clicks=0, style={'marginLeft': '5px'}), trips_table @@ -210,9 +218,8 @@ def render_content(tab, store_uuids, store_excluded_uuids, store_trips, store_de elif tab == 'tab-trajectories-datatable': (start_date, end_date) = iso_to_date_only(start_date, end_date) - # Fetch new data based on the selected key_list from the keylist-switch - if store_trajectories == {} or key_list: # Ensure data is refreshed when key_list changes + if store_trajectories == {} or key_list: store_trajectories = update_store_trajectories(start_date, end_date, timezone, store_excluded_uuids, key_list) data = store_trajectories.get("data", []) @@ -223,13 +230,9 @@ def render_content(tab, store_uuids, store_excluded_uuids, store_trips, store_de df = pd.DataFrame(data) if df.empty or not has_perm: - # If no permission or data, disable interval and return empty content return None, loaded_uuids_store, True - # Filter the columns based on permissions df = df.drop(columns=[col for col in df.columns if col not in columns]) - - # Return the populated DataTable return populate_datatable(df), loaded_uuids_store, True # Default case: if no data is loaded or the tab is not handled @@ -277,11 +280,12 @@ def update_dropdowns_trips(n_clicks, button_label): #return the list of hidden columns and the updated button label return hidden_col, button_label -def populate_datatable(df, table_id=''): + +def populate_datatable(df, table_id='', page_current=0): if not isinstance(df, pd.DataFrame): raise PreventUpdate return dash_table.DataTable( - id= table_id, + id=table_id, # columns=[{"name": i, "id": i} for i in df.columns], data=df.to_dict('records'), export_format="csv", @@ -289,7 +293,7 @@ def populate_datatable(df, table_id=''): # filter_action="native", sort_action="native", # give user capability to sort columns sort_mode="single", # sort across 'multi' or 'single' columns - page_current=0, # page number that user is on + page_current=page_current, # set to current page page_size=50, # number of rows visible per page style_cell={ 'textAlign': 'left',