From 8d693c48c742df3137cbc7ab9c08daecdc08dcf7 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 9 Aug 2025 01:05:19 +0200 Subject: [PATCH] =?UTF-8?q?Actualizaci=C3=B3n=20de=20application=5Fevents.?= =?UTF-8?q?json=20con=20nuevos=20eventos=20para=20la=20creaci=C3=B3n,=20el?= =?UTF-8?q?iminaci=C3=B3n=20y=20actualizaci=C3=B3n=20de=20sesiones=20de=20?= =?UTF-8?q?plot.=20Se=20ajustaron=20las=20fechas=20de=20=C3=BAltima=20actu?= =?UTF-8?q?alizaci=C3=B3n=20en=20varios=20archivos=20de=20configuraci?= =?UTF-8?q?=C3=B3n,=20incluyendo=20plc=5Fdatasets.json,=20plot=5Fsessions.?= =?UTF-8?q?json=20y=20system=5Fstate.json.=20Se=20implementaron=20controle?= =?UTF-8?q?s=20de=20tasa=20de=20refresco=20y=20ventana=20de=20tiempo=20en?= =?UTF-8?q?=20la=20interfaz=20de=20usuario,=20mejorando=20la=20experiencia?= =?UTF-8?q?=20de=20usuario=20en=20la=20visualizaci=C3=B3n=20de=20datos=20e?= =?UTF-8?q?n=20tiempo=20real.=20Adem=C3=A1s,=20se=20integr=C3=B3=20el=20pl?= =?UTF-8?q?ugin=20de=20zoom=20en=20los=20gr=C3=A1ficos=20para=20una=20mejo?= =?UTF-8?q?r=20interacci=C3=B3n.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application_events.json | 313 +++++++++++++++++++++++++++++++++++++++- plc_datasets.json | 2 +- plot_sessions.json | 10 +- static/js/plotting.js | 208 ++++++++++++++++++++++++++ static/js/tabs.js | 23 +++ system_state.json | 2 +- templates/index.html | 1 + 7 files changed, 550 insertions(+), 9 deletions(-) diff --git a/application_events.json b/application_events.json index 5226adf..0fc7e0a 100644 --- a/application_events.json +++ b/application_events.json @@ -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 } \ No newline at end of file diff --git a/plc_datasets.json b/plc_datasets.json index ddbf3df..6dc3ee1 100644 --- a/plc_datasets.json +++ b/plc_datasets.json @@ -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" } \ No newline at end of file diff --git a/plot_sessions.json b/plot_sessions.json index 8849ad1..0215368 100644 --- a/plot_sessions.json +++ b/plot_sessions.json @@ -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" } \ No newline at end of file diff --git a/static/js/plotting.js b/static/js/plotting.js index 7ffbb8b..3165126 100644 --- a/static/js/plotting.js +++ b/static/js/plotting.js @@ -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; diff --git a/static/js/tabs.js b/static/js/tabs.js index 42b0c2b..d1087a7 100644 --- a/static/js/tabs.js +++ b/static/js/tabs.js @@ -116,6 +116,29 @@ class TabManager {
+
+
+ + + ms +
+
+ + + s +
+
+ +
+
diff --git a/system_state.json b/system_state.json index 3170e15..729b631 100644 --- a/system_state.json +++ b/system_state.json @@ -7,5 +7,5 @@ ] }, "auto_recovery_enabled": true, - "last_update": "2025-08-08T19:35:31.456173" + "last_update": "2025-08-09T01:04:33.338067" } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 73330b4..41caee0 100644 --- a/templates/index.html +++ b/templates/index.html @@ -758,6 +758,7 @@ +