Implement infinite scroll for logs table

This commit is contained in:
2026-07-22 21:39:47 +02:00
parent e0a4d098d9
commit 54fd8ce74c
2 changed files with 79 additions and 24 deletions
+4 -2
View File
@@ -1,4 +1,4 @@
# File: logs_view.py # File: logs/view.py
# Copyright (C) 2026 Erick Ahmed # Copyright (C) 2026 Erick Ahmed
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -70,5 +70,7 @@ def get_logs_table_component():
'textAlign': 'center', 'textAlign': 'center',
'fontFamily': 'Segoe UI, Arial, sans-serif' 'fontFamily': 'Segoe UI, Arial, sans-serif'
} }
) ),
html.Button("Load More", id="load-more-logs-btn", n_clicks=0, style={'display': 'none'}),
html.Div(id='dummy-output', style={'display': 'none'})
]) ])
+75 -22
View File
@@ -8,7 +8,7 @@ from concurrent.futures import ThreadPoolExecutor
import polars as pl import polars as pl
import dash import dash
from dash import dcc, html, Input, Output from dash import dcc, html, Input, Output, State
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
import numpy as np import numpy as np
@@ -19,7 +19,7 @@ from stats.id_viewer import _format_can_id_vec, plot_bits
from stats.frequency import calculate_frequency, plot_frequency from stats.frequency import calculate_frequency, plot_frequency
from stats.correlation import calculate_correlation, plot_correlation_heatmap from stats.correlation import calculate_correlation, plot_correlation_heatmap
from stats.entropy import calculate_byte_entropy, plot_entropy_heatmap from stats.entropy import calculate_byte_entropy, plot_entropy_heatmap
from logs.view import prepare_logs_data, get_logs_table_component from logs.view import get_logs_table_component, prepare_logs_data
RAW_LOG_DIR = "data/logs" RAW_LOG_DIR = "data/logs"
@@ -86,7 +86,6 @@ for log_file in Path(RAW_LOG_DIR).glob("*.txt"):
PRECOMPUTED_FIGURES = {} PRECOMPUTED_FIGURES = {}
DATA_BY_ID = {} DATA_BY_ID = {}
CORR_CACHE = {} CORR_CACHE = {}
LOGS_CACHE = {}
def process_bus_data(vehicle, bus, df): def process_bus_data(vehicle, bus, df):
precomp = {} precomp = {}
@@ -177,31 +176,85 @@ app.layout = dbc.Container([
], id="main-tabs", active_tab="statistics") ], id="main-tabs", active_tab="statistics")
], fluid=True) ], fluid=True)
@app.callback( app.clientside_callback(
[Output('logs-table', 'data'), """
Output('logs-table', 'columns'), function(data) {
Output('logs-info-text', 'children')], if (!data) return '';
Input('logs-vehicle-selector', 'value'), setTimeout(function() {
Input('logs-bus-selector', 'value') const btn = document.getElementById('load-more-logs-btn');
const info = document.getElementById('logs-info-text');
if (!btn || !info || info.innerText.toLowerCase().includes('all')) {
return;
}
btn.dataset.loading = "false";
const containers = document.querySelectorAll('.dash-spreadsheet-container, .dash-spreadsheet-inner');
if (containers.length === 0) return;
containers.forEach(container => {
container.onscroll = function() {
if (container.scrollHeight - container.scrollTop - container.clientHeight < 200) {
if (btn && btn.dataset.loading === "false") {
btn.dataset.loading = "true";
btn.click();
}
}
};
if (container.scrollHeight - container.scrollTop - container.clientHeight < 200) {
if (btn && btn.dataset.loading === "false") {
btn.dataset.loading = "true";
btn.click();
}
}
});
}, 200);
return '';
}
""",
Output('dummy-output', 'children'),
Input('logs-table', 'data')
) )
def update_logs_table(vehicle, bus):
@app.callback(
Output('logs-table', 'data'),
Output('logs-table', 'columns'),
Output('logs-info-text', 'children'),
Input('logs-vehicle-selector', 'value'),
Input('logs-bus-selector', 'value'),
Input('load-more-logs-btn', 'n_clicks'),
State('logs-table', 'data'),
)
def update_logs_table(vehicle, bus, n_clicks, current_data):
ctx = dash.callback_context
trigger_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else ''
if not vehicle or not bus or vehicle not in DATA or bus not in DATA[vehicle]: if not vehicle or not bus or vehicle not in DATA or bus not in DATA[vehicle]:
return [], [], "No data available" return [], [], "No data available"
cache_key = (vehicle, bus) df = DATA[vehicle][bus]
if cache_key not in LOGS_CACHE: prepared_df = prepare_logs_data(df)
display_df = prepare_logs_data(DATA[vehicle][bus])
LOGS_CACHE[cache_key] = display_df
else:
display_df = LOGS_CACHE[cache_key]
total_rows = len(display_df) total_rows = len(prepared_df)
columns = [{"name": col, "id": col} for col in display_df.columns] columns = [{"name": i, "id": i} for i in prepared_df.columns]
data = display_df.to_dict('records')
info_text = f"Displaying {total_rows} frames." if trigger_id in ['logs-vehicle-selector', 'logs-bus-selector', '']:
current_data = []
return data, columns, info_text offset = len(current_data) if current_data else 0
chunk_size = 1000
if offset >= total_rows:
return current_data, columns, f"Displaying all {total_rows} total frames."
next_chunk = prepared_df.iloc[offset:offset + chunk_size].to_dict('records')
new_data = current_data + next_chunk
new_offset = len(new_data)
info_text = f"Displaying {new_offset} of {total_rows} total frames."
return new_data, columns, info_text
@app.callback( @app.callback(
Output('tab-content', 'children'), Output('tab-content', 'children'),
@@ -306,4 +359,4 @@ def update_corr(method, target, vehicle, bus, tab):
return plot_correlation_heatmap(corr_df, target_id=target_id, title=title) return plot_correlation_heatmap(corr_df, target_id=target_id, title=title)
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=False) app.run(debug=False)