Actualización de application_events.json con nuevos eventos para la creación, eliminación y actualización de sesiones de plot. Se ajustaron las fechas de última actualización en varios archivos de configuración, incluyendo plc_datasets.json, plot_sessions.json y system_state.json. Se implementaron controles de tasa de refresco y ventana de tiempo en la interfaz de usuario, mejorando la experiencia de usuario en la visualización de datos en tiempo real. Además, se integró el plugin de zoom en los gráficos para una mejor interacción.

This commit is contained in:
Miguel 2025-08-09 01:05:19 +02:00
parent f78ccbdc1d
commit 8d693c48c7
7 changed files with 550 additions and 9 deletions

View File

@ -7836,8 +7836,317 @@
"activated_datasets": 1,
"total_datasets": 1
}
},
{
"timestamp": "2025-08-08T19:38:56.880475",
"level": "info",
"event_type": "plot_session_removed",
"message": "Plot session 'UR29' removed",
"details": {
"session_id": "plot_20"
}
},
{
"timestamp": "2025-08-08T20:00:43.326432",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-08T20:00:43.380505",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "DAR",
"variables_count": 2,
"streaming_count": 2,
"prefix": "gateway_phoenix"
}
},
{
"timestamp": "2025-08-08T20:00:43.401098",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 1
}
},
{
"timestamp": "2025-08-08T20:01:26.642290",
"level": "info",
"event_type": "plot_session_created",
"message": "Plot session 'UR29' created and started",
"details": {
"session_id": "plot_0",
"variables": [
"UR29_Brix",
"UR29_ma"
],
"time_window": 60,
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-08-08T20:07:27.277526",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-08T20:07:27.342707",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "DAR",
"variables_count": 2,
"streaming_count": 2,
"prefix": "gateway_phoenix"
}
},
{
"timestamp": "2025-08-08T20:07:27.352708",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 1
}
},
{
"timestamp": "2025-08-08T20:08:04.536885",
"level": "info",
"event_type": "plot_session_updated",
"message": "Plot session 'UR29' configuration updated",
"details": {
"session_id": "plot_0",
"new_config": {
"name": "UR29",
"variables": [
"UR29_Brix",
"UR29_ma"
],
"time_window": 10,
"y_min": null,
"y_max": null,
"trigger_variable": null,
"trigger_enabled": false,
"trigger_on_true": true
}
}
},
{
"timestamp": "2025-08-08T20:08:07.955693",
"level": "info",
"event_type": "plot_session_updated",
"message": "Plot session 'UR29' configuration updated",
"details": {
"session_id": "plot_0",
"new_config": {
"name": "UR29",
"variables": [
"UR29_Brix",
"UR29_ma"
],
"time_window": 30,
"y_min": null,
"y_max": null,
"trigger_variable": null,
"trigger_enabled": false,
"trigger_on_true": true
}
}
},
{
"timestamp": "2025-08-08T20:10:10.377616",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-08T20:10:10.444457",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "DAR",
"variables_count": 2,
"streaming_count": 2,
"prefix": "gateway_phoenix"
}
},
{
"timestamp": "2025-08-08T20:10:10.457492",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 1
}
},
{
"timestamp": "2025-08-08T20:12:14.116200",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-08T20:12:14.185262",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "DAR",
"variables_count": 2,
"streaming_count": 2,
"prefix": "gateway_phoenix"
}
},
{
"timestamp": "2025-08-08T20:12:14.194260",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 1
}
},
{
"timestamp": "2025-08-09T00:58:14.018492",
"level": "info",
"event_type": "plot_session_updated",
"message": "Plot session 'UR29' configuration updated",
"details": {
"session_id": "plot_0",
"new_config": {
"name": "UR29",
"variables": [
"UR29_Brix",
"UR29_ma"
],
"time_window": 10,
"y_min": null,
"y_max": null,
"trigger_variable": null,
"trigger_enabled": false,
"trigger_on_true": true
}
}
},
{
"timestamp": "2025-08-09T00:58:18.412418",
"level": "info",
"event_type": "plot_session_updated",
"message": "Plot session 'UR29' configuration updated",
"details": {
"session_id": "plot_0",
"new_config": {
"name": "UR29",
"variables": [
"UR29_Brix",
"UR29_ma"
],
"time_window": 120,
"y_min": null,
"y_max": null,
"trigger_variable": null,
"trigger_enabled": false,
"trigger_on_true": true
}
}
},
{
"timestamp": "2025-08-09T00:59:29.400804",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-09T00:59:29.499648",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "DAR",
"variables_count": 2,
"streaming_count": 2,
"prefix": "gateway_phoenix"
}
},
{
"timestamp": "2025-08-09T00:59:29.508648",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 1
}
},
{
"timestamp": "2025-08-09T00:59:52.220857",
"level": "info",
"event_type": "plot_session_updated",
"message": "Plot session 'UR29' configuration updated",
"details": {
"session_id": "plot_0",
"new_config": {
"name": "UR29",
"variables": [
"UR29_Brix",
"UR29_ma"
],
"time_window": 50,
"y_min": null,
"y_max": null,
"trigger_variable": null,
"trigger_enabled": false,
"trigger_on_true": true
}
}
},
{
"timestamp": "2025-08-09T01:04:33.254115",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-08-09T01:04:33.318060",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "DAR",
"variables_count": 2,
"streaming_count": 2,
"prefix": "gateway_phoenix"
}
},
{
"timestamp": "2025-08-09T01:04:33.328051",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 1
}
}
],
"last_updated": "2025-08-08T19:35:31.446174",
"total_entries": 742
"last_updated": "2025-08-09T01:04:33.328051",
"total_entries": 767
}

View File

@ -33,5 +33,5 @@
],
"current_dataset_id": "DAR",
"version": "1.0",
"last_update": "2025-08-08T19:35:31.435189"
"last_update": "2025-08-09T01:04:33.316045"
}

View File

@ -1,21 +1,21 @@
{
"plots": {
"plot_20": {
"plot_0": {
"name": "UR29",
"variables": [
"UR29_Brix",
"UR29_ma"
],
"time_window": 10,
"time_window": 50,
"y_min": null,
"y_max": null,
"trigger_variable": null,
"trigger_enabled": false,
"trigger_on_true": true,
"session_id": "plot_20"
"session_id": "plot_0"
}
},
"session_counter": 21,
"last_saved": "2025-08-08T16:24:02.832199",
"session_counter": 1,
"last_saved": "2025-08-09T00:59:52.219460",
"version": "1.0"
}

View File

@ -37,6 +37,19 @@ class PlotManager {
this.init();
}
// Asegura el toggle correcto de la sección de Trigger dentro del formulario (usado en edición)
togglePlotFormTriggerConfig() {
try {
const triggerEnabled = document.getElementById('plot-form-trigger-enabled');
const triggerConfig = document.getElementById('plot-form-trigger-config');
if (triggerConfig && triggerEnabled) {
triggerConfig.style.display = triggerEnabled.checked ? 'block' : 'none';
}
} catch (e) {
console.warn('togglePlotFormTriggerConfig failed', e);
}
}
init() {
// Cargar sesiones existentes al inicializar
this.loadExistingSessions();
@ -284,6 +297,27 @@ class PlotManager {
tooltip: {
mode: 'index',
intersect: false
},
// Integración con chartjs-plugin-zoom (pan/zoom en eje X)
zoom: {
pan: {
enabled: true,
mode: 'x'
},
zoom: {
pinch: { enabled: true },
wheel: { enabled: true },
mode: 'x'
},
limits: {
x: {
// Permitir variar delay/duration en un rango razonable
minDelay: 0,
maxDelay: 4000,
minDuration: 1000,
maxDuration: (config.time_window || 60) * 1000
}
}
}
},
@ -783,6 +817,23 @@ class PlotManager {
// Actualizar estadísticas del plot
this.updatePlotStats(sessionId, sessionInfo);
// Sincronizar controles del sub-tab (refresh rate y time window)
try {
const refreshInput = document.getElementById(`refresh-rate-tab-${sessionId}`);
if (refreshInput) {
refreshInput.value = this.refreshRates.get(sessionId) || 1000;
}
const timeWindowInput = document.getElementById(`time-window-tab-${sessionId}`);
if (timeWindowInput) {
const sessionData = this.sessions.get(sessionId);
const currentWindow = (sessionData && sessionData.config && sessionData.config.time_window) ? sessionData.config.time_window : (plotConfig.time_window || 60);
timeWindowInput.value = currentWindow;
}
} catch (e) {
console.warn('Could not sync plot controls for session', sessionId, e);
}
}
updatePlotStats(sessionId, sessionInfo) {
@ -1539,6 +1590,152 @@ class PlotManager {
console.error('❌ Error updating refresh rate:', error);
}
}
/**
* Actualiza dinámicamente la ventana de tiempo (span) del plot sin recrearlo
*/
updateTimeWindow(sessionId, timeWindowSeconds) {
try {
const seconds = parseInt(timeWindowSeconds);
if (isNaN(seconds)) return;
const clampedSeconds = Math.min(Math.max(seconds, 5), 3600);
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart) return;
// Guardar en la config local
sessionData.config = sessionData.config || {};
sessionData.config.time_window = clampedSeconds;
const chart = sessionData.chart;
if (sessionData.isRealTimeMode) {
// Modo realtime: ajustar duración de la escala en ms
const ms = clampedSeconds * 1000;
if (chart.scales && chart.scales.x && chart.scales.x.realtime) {
chart.scales.x.realtime.duration = ms;
}
if (chart.options && chart.options.scales && chart.options.scales.x && chart.options.scales.x.realtime) {
chart.options.scales.x.realtime.duration = ms;
}
chart.update('quiet');
} else {
// Modo fallback: limpiar datos fuera de la nueva ventana y refrescar
this.cleanupOldDataFallback(sessionId);
chart.update('quiet');
}
} catch (error) {
console.error('❌ Error updating time window:', error);
}
}
/**
* Handler para cambios del control de ventana de tiempo en UI, y persistencia en backend
*/
async onTimeWindowChange(sessionId, timeWindowSeconds) {
this.updateTimeWindow(sessionId, timeWindowSeconds);
try {
const sessionData = this.sessions.get(sessionId);
if (!sessionData) return;
const baseConfig = sessionData.config || {};
const payload = {
name: baseConfig.name || `Plot ${sessionId}`,
variables: Array.isArray(baseConfig.variables) ? baseConfig.variables : [],
time_window: parseInt(timeWindowSeconds) || baseConfig.time_window || 60,
y_min: baseConfig.y_min ?? null,
y_max: baseConfig.y_max ?? null,
trigger_enabled: baseConfig.trigger_enabled || false,
trigger_variable: baseConfig.trigger_variable || null,
trigger_on_true: baseConfig.trigger_on_true !== false,
};
const response = await fetch(`/api/plots/${sessionId}/config`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await response.json();
if (result && result.success) {
sessionData.config = { ...baseConfig, ...payload };
this.updatePlotStats(sessionId, sessionData.config);
showNotification(result.message || 'Plot updated', 'success');
} else if (result && result.error) {
showNotification(result.error, 'error');
}
} catch (error) {
console.error('❌ Error persisting time window:', error);
}
}
/**
* Aplica una configuración de plot al chart existente sin recrearlo
*/
applyConfigToChart(sessionId, config) {
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart) return;
// Guardar config local
sessionData.config = { ...(sessionData.config || {}), ...config };
// 1) Actualizar ventana de tiempo
if (typeof config.time_window !== 'undefined') {
this.updateTimeWindow(sessionId, config.time_window);
}
// 2) Actualizar rango Y
const chart = sessionData.chart;
if (chart.options && chart.options.scales && chart.options.scales.y) {
chart.options.scales.y.min = config.y_min ?? undefined;
chart.options.scales.y.max = config.y_max ?? undefined;
}
// 3) Actualizar datasets según variables
if (Array.isArray(config.variables)) {
const oldDatasets = chart.data.datasets || [];
const oldByLabel = new Map();
oldDatasets.forEach(ds => {
if (ds && typeof ds.label === 'string') oldByLabel.set(ds.label, ds);
});
const newDatasets = config.variables.map((variable, index) => {
const existing = oldByLabel.get(variable);
const color = this.getColor(variable, index);
if (existing) {
existing.label = variable;
existing.borderColor = color;
existing.backgroundColor = color + '20';
existing.borderWidth = 2;
existing.fill = false;
existing.pointRadius = 0;
existing.pointHoverRadius = 3;
existing.tension = 0.1;
return existing;
}
return {
label: variable,
data: [],
borderColor: color,
backgroundColor: color + '20',
borderWidth: 2,
fill: false,
pointRadius: 0,
pointHoverRadius: 3,
tension: 0.1
};
});
chart.data.datasets = newDatasets;
// Recalcular índices y timestamps de empuje
sessionData.datasetIndex = new Map();
sessionData.lastPushedXByDataset = new Map();
config.variables.forEach((variable, idx) => sessionData.datasetIndex.set(variable, idx));
}
chart.update('quiet');
}
}
// Funciones de debug removidas para limpiar console.log
@ -1661,6 +1858,17 @@ function initializePlotManager() {
return;
}
// Intentar registrar plugin de zoom (UMD) con soporte a default export
try {
// chartjs-plugin-zoom UMD expone global ChartZoom
if (window.ChartZoom) {
try { Chart.register(window.ChartZoom); } catch (_) { }
}
console.log('🔎 Zoom plugin registration attempted. has ChartZoom =', !!window.ChartZoom);
} catch (e) {
console.warn('⚠️ Zoom plugin registration attempt failed:', e);
}
// Verificar si RealTimeScale está disponible
const hasRealTimeScale = !!Chart.registry.scales.realtime;

View File

@ -116,6 +116,29 @@ class TabManager {
</span>
</div>
<div class="plot-canvas">
<div style="display:flex; justify-content: space-between; align-items:center; gap: 0.5rem; margin-bottom: 0.5rem;">
<div class="refresh-rate-control">
<label for="refresh-rate-tab-${sessionId}" title="Chart Refresh Rate (ms)"></label>
<input type="number" id="refresh-rate-tab-${sessionId}" class="refresh-rate-input"
value="1000" min="100" max="60000" step="100"
onchange="plotManager.updateRefreshRate('${sessionId}', this.value)"
onkeypress="if(event.key==='Enter') plotManager.updateRefreshRate('${sessionId}', this.value)"
title="Refresh rate in milliseconds (100-60000)">
<span class="refresh-rate-unit">ms</span>
</div>
<div class="time-window-control">
<label for="time-window-tab-${sessionId}" title="Time Window (seconds)">🕒</label>
<input type="number" id="time-window-tab-${sessionId}" class="time-window-input"
value="60" min="5" max="3600" step="5"
onchange="plotManager.onTimeWindowChange('${sessionId}', this.value)"
onkeypress="if(event.key==='Enter') plotManager.onTimeWindowChange('${sessionId}', this.value)"
title="Visible time window in seconds (5-3600)">
<span class="time-window-unit">s</span>
</div>
<div class="zoom-controls">
<button type="button" class="outline" title="Reset Zoom" onclick="(function(){ try { const s=plotManager.sessions.get('${sessionId}'); if(s&&s.chart&&typeof s.chart.resetZoom==='function'){ s.chart.resetZoom('none'); } } catch(e){ console.warn(e);} })()">🔍 Reset</button>
</div>
</div>
<canvas id="chart-${sessionId}"></canvas>
</div>
</div>

View File

@ -7,5 +7,5 @@
]
},
"auto_recovery_enabled": true,
"last_update": "2025-08-08T19:35:31.456173"
"last_update": "2025-08-09T01:04:33.338067"
}

View File

@ -758,6 +758,7 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1"></script>
<script src="https://cdn.jsdelivr.net/npm/luxon@2"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.3.1"></script>
<script src="https://unpkg.com/chartjs-plugin-zoom@1.2.1/dist/chartjs-plugin-zoom.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@2.0.0"></script>
<!-- JavaScript Modules -->