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
# SPDX-License-Identifier: AGPL-3.0-or-later
@@ -70,5 +70,7 @@ def get_logs_table_component():
'textAlign': 'center',
'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 dash
from dash import dcc, html, Input, Output
from dash import dcc, html, Input, Output, State
import dash_bootstrap_components as dbc
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.correlation import calculate_correlation, plot_correlation_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"
@@ -86,7 +86,6 @@ for log_file in Path(RAW_LOG_DIR).glob("*.txt"):
PRECOMPUTED_FIGURES = {}
DATA_BY_ID = {}
CORR_CACHE = {}
LOGS_CACHE = {}
def process_bus_data(vehicle, bus, df):
precomp = {}
@@ -177,31 +176,85 @@ app.layout = dbc.Container([
], id="main-tabs", active_tab="statistics")
], fluid=True)
@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')
app.clientside_callback(
"""
function(data) {
if (!data) return '';
setTimeout(function() {
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]:
return [], [], "No data available"
cache_key = (vehicle, bus)
if cache_key not in LOGS_CACHE:
display_df = prepare_logs_data(DATA[vehicle][bus])
LOGS_CACHE[cache_key] = display_df
else:
display_df = LOGS_CACHE[cache_key]
df = DATA[vehicle][bus]
prepared_df = prepare_logs_data(df)
total_rows = len(display_df)
columns = [{"name": col, "id": col} for col in display_df.columns]
data = display_df.to_dict('records')
total_rows = len(prepared_df)
columns = [{"name": i, "id": i} for i in prepared_df.columns]
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(
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)
if __name__ == '__main__':
app.run(debug=False)
app.run(debug=False)