Actualización de archivos de configuración y estado del sistema, incluyendo la adición de nuevos eventos en application_events.json para la gestión de sesiones de plot y mejoras en el sistema de streaming. Se implementaron funciones de depuración en plotting.js y se ajustaron las fechas de última actualización en varios archivos. Además, se mejoró la interfaz de usuario en index.html y se realizaron ajustes en la gestión de sesiones en PlotManager para optimizar la experiencia del usuario.

This commit is contained in:
Miguel 2025-07-22 09:24:25 +02:00
parent 8195b5d430
commit 55c4e67cf7
22 changed files with 5412 additions and 1025 deletions

View File

@ -0,0 +1,198 @@
# 📈 Chart.js Plugin Streaming - Integración
## 🚀 Implementación Exitosa
Se ha integrado exitosamente **chartjs-plugin-streaming** en la aplicación PLC S7-315 Streamer & Logger para mejorar significativamente el sistema de plotting en tiempo real.
## 📋 ¿Qué se ha cambiado?
### 1. **Archivos agregados**
```
static/js/chartjs-streaming/
├── chartjs-plugin-streaming.js # 📦 Archivo integrador principal
├── plugin.streaming.js # 🔧 Plugin de streaming
├── plugin.zoom.js # 🔍 Plugin de zoom
├── scale.realtime.js # ⏰ Escala de tiempo real
└── helpers.streaming.js # 🛠️ Utilidades
```
### 2. **Archivos modificados**
- `templates/index.html` - Carga de nueva librería
- `static/js/plotting.js` - Sistema de plotting reescrito para streaming
## 🔧 Características Implementadas
### ✅ **Ventajas del nuevo sistema:**
1. **📊 Streaming automático**: Los gráficos se actualizan automáticamente
2. **🔄 Gestión inteligente de memoria**: Elimina datos antiguos automáticamente
3. **⚡ Mejor rendimiento**: Optimizado para datos en tiempo real
4. **🎛️ Controles avanzados**: Pause/Resume, Clear, mejor interactividad
5. **📏 Escalas dinámicas**: Ventana de tiempo deslizante automática
6. **🎨 Configuración flexible**: Duración, frecuencia, límites Y personalizables
### ✅ **Funcionalidades disponibles:**
#### **Configuración automática**
```javascript
// Se crea automáticamente con configuración optimizada
const config = ChartStreaming.createStreamingChartConfig({
duration: 60000, // 60 segundos de ventana
refresh: 500, // Actualizar cada 500ms
frameRate: 30, // 30 FPS
yMin: -100, // Límite inferior Y
yMax: 100 // Límite superior Y
});
```
#### **Controles de streaming**
- **▶️ Start**: Inicia el streaming en tiempo real
- **⏸️ Pause**: Pausa temporalmente el streaming
- **🗑️ Clear**: Limpia todos los datos del gráfico
- **⏹️ Stop**: Detiene completamente el streaming
#### **Gestión automática de datos**
- Los datos se agregan automáticamente conforme llegan
- Los datos antiguos se eliminan según la configuración TTL
- La ventana de tiempo se desliza automáticamente
## 🎯 Beneficios para el Usuario
### **Antes (Sistema manual)**
```javascript
// Gestión manual de escalas y datos
chart.data.datasets = plotData.datasets;
chart.options.scales.x.min = startTime;
chart.options.scales.x.max = endTime;
chart.update('none');
```
### **Ahora (Sistema streaming)**
```javascript
// Automático - solo agregar datos
ChartStreaming.addStreamingData(chart, datasetIndex, {
x: timestamp,
y: value
});
// El plugin maneja todo lo demás automáticamente
```
## 🔧 Configuración por Defecto
### **Escalas de tiempo real**
- **Duración**: 60 segundos por defecto (configurable por plot)
- **Refresco**: 500ms (datos se obtienen automáticamente del backend)
- **Frame rate**: 30 FPS para animaciones suaves
- **TTL**: Configurable para limpieza automática de datos
### **Optimizaciones de rendimiento**
- Sin animaciones innecesarias
- Puntos de datos ocultos (solo visible en hover)
- Líneas suaves con tensión optimizada
- Actualización silenciosa ("quiet mode")
## 📚 API Disponible
### **Funciones principales:**
```javascript
// Control global
window.ChartStreaming.createStreamingChartConfig(options)
window.ChartStreaming.addStreamingData(chart, datasetIndex, data)
window.ChartStreaming.setStreamingPause(chart, paused)
window.ChartStreaming.clearStreamingData(chart)
// Control por sesión (PlotManager)
plotManager.setStreamingPause(sessionId, paused)
plotManager.clearStreamingData(sessionId)
plotManager.refreshStreamingData(sessionId, chart) // Automática
```
### **Configuración personalizada:**
```javascript
{
duration: 60000, // Ventana de tiempo en ms
delay: 0, // Retraso en ms
refresh: 500, // Intervalo de actualización en ms
frameRate: 30, // FPS para animaciones
pause: false, // Estado inicial
ttl: undefined, // Tiempo de vida de datos
yMin: undefined, // Límite inferior Y
yMax: undefined, // Límite superior Y
onRefresh: function // Callback para obtener datos
}
```
## 🔗 Integración con Backend
### **Flujo de datos actualizado:**
1. **Backend**: Genera datos en `/api/plots/{sessionId}/data`
2. **Plugin**: Llama automáticamente `onRefresh` cada 500ms
3. **PlotManager**: Obtiene datos del backend en `refreshStreamingData`
4. **Chart**: Se actualiza automáticamente con nuevos datos
### **Compatibilidad:**
- ✅ Funciona con todos los endpoints existentes
- ✅ Compatible con triggers booleanos
- ✅ Mantiene configuración Y min/max
- ✅ Preserva colores y estilos de variables
## 🎮 Controles de Usuario
### **Interfaz actualizada:**
- Los botones **Start/Pause/Clear/Stop** ahora controlan streaming
- **Pause**: Congela la visualización manteniendo datos
- **Clear**: Limpia gráfico pero mantiene configuración
- **Stop**: Pausa streaming y notifica al backend
### **Retrocompatibilidad:**
- Todas las funciones existentes siguen funcionando
- Los plots existentes se migran automáticamente
- La API del backend no ha cambiado
## 🔬 Debug y Troubleshooting
### **Habilitar debug:**
```javascript
// En consola del navegador
enablePlotDebug()
```
### **Logs disponibles:**
- Inicialización de datasets de streaming
- Agregado de nuevos puntos de datos
- Control de pause/resume
- Limpieza de datos
- Errores de conexión con backend
### **Verificar integración:**
```javascript
// En consola del navegador
testPlotSystem()
```
## 🚀 Próximos Pasos Recomendados
1. **✅ Probar con datos reales** del PLC
2. **🎛️ Ajustar configuraciones** según necesidades específicas
3. **📊 Optimizar intervalos** de refresco según carga del sistema
4. **🔧 Personalizar colores** y estilos según preferencias
## 💡 Tips de Uso
### **Para mejor rendimiento:**
- Usa intervalos de refresco ≥ 500ms para reducir carga
- Configura TTL para limpiar datos antiguos automáticamente
- Mantén ≤ 10 variables por plot para fluidez óptima
### **Para debugging:**
- Activa logs de debug cuando desarrolles
- Usa la consola del navegador para inspeccionar
- Verifica conectividad PLC antes de crear plots
---
## 🎉 ¡Integración Completada!
La aplicación ahora cuenta con un sistema de plotting en tiempo real robusto, eficiente y fácil de usar, potenciado por **chartjs-plugin-streaming**.
**¡Disfruta de tus gráficos en tiempo real mejorados!** 📈✨

View File

@ -0,0 +1,247 @@
# 🔧 Troubleshooting Chart.js Streaming - Guía de Resolución
## 🚨 Problema Reportado
**Síntomas:**
- ✅ Status muestra "Active"
- ✅ Variables cambia de 0 a 1
- ❌ Data Points se mantiene en 0
- ❌ No se ve ningún plot dentro de la grilla
- ❌ La línea de tiempo no se mueve
- ⚠️ La escala Y cambia pero es lo único que funciona
## 🔍 Diagnóstico Paso a Paso
### **Paso 1: Verificar que se cargó chartjs-plugin-streaming**
Abrir **Consola del Navegador** (F12) y ejecutar:
```javascript
verifyStreamingIntegration()
```
**Resultado esperado:**
```
🧪 Verificando integración de Chart.js Streaming...
✅ Chart.js cargado: true
✅ ChartStreaming cargado: true
✅ PlotManager cargado: true
✅ Sesiones de streaming activas: 1
```
**Si ChartStreaming cargado: false:**
1. Verificar que `chartjs-plugin-streaming.js` se carga correctamente
2. Revisar errores en la consola
3. Recargar la página
### **Paso 2: Habilitar Debug Detallado**
```javascript
enablePlotDebug()
```
### **Paso 3: Forzar Actualización de Datos**
```javascript
forceStreamingUpdate()
```
**Buscar en consola:**
```
📈 Plot plot_13: Fetching data from backend...
📈 Plot plot_13: Received data: {...}
📈 Plot plot_13: Processing X datasets for streaming
```
### **Paso 4: Verificar Datos del Backend**
Ejecutar en consola:
```javascript
fetch('/api/plots/plot_13/data')
.then(r => r.json())
.then(data => {
console.log('📊 Backend data:', data);
console.log('📊 Datasets:', data.datasets?.length || 0);
console.log('📊 Data points per dataset:',
data.datasets?.map(d => d.data?.length || 0) || []);
});
```
**Resultado esperado:**
```
📊 Backend data: {session_id: "plot_13", datasets: [...], data_points_count: X}
📊 Datasets: 1
📊 Data points per dataset: [5, 8, 12]
```
### **Paso 5: Verificar Configuración del Chart**
```javascript
// Para la sesión activa (ej: plot_13)
const sessionData = plotManager.sessions.get('plot_13');
console.log('📈 Chart config:', {
hasChart: !!sessionData?.chart,
scaleType: sessionData?.chart?.scales?.x?.type,
hasRealTimeScale: sessionData?.chart?.scales?.x?.constructor?.name,
streamingEnabled: !!sessionData?.chart?.$streaming?.enabled,
datasets: sessionData?.chart?.data?.datasets?.length || 0
});
```
**Resultado esperado:**
```
📈 Chart config: {
hasChart: true,
scaleType: "realtime",
hasRealTimeScale: "RealTimeScale",
streamingEnabled: true,
datasets: 1
}
```
## 🛠️ Soluciones Comunes
### **Problema: ChartStreaming no está cargado**
**Causa:** El archivo `chartjs-plugin-streaming.js` no se carga correctamente.
**Solución:**
1. Verificar que el archivo existe en `static/js/chartjs-streaming/chartjs-plugin-streaming.js`
2. Revisar que el HTML incluye: `<script src="/static/js/chartjs-streaming/chartjs-plugin-streaming.js"></script>`
3. Verificar orden de carga (debe ser después de Chart.js y antes de plotting.js)
### **Problema: Backend devuelve datos pero no aparecen en el chart**
**Causa:** Error en el procesamiento de datos o timestamps incorrectos.
**Solución:**
```javascript
// Verificar timestamps de los datos
fetch('/api/plots/plot_13/data')
.then(r => r.json())
.then(data => {
const firstDataset = data.datasets[0];
const firstPoint = firstDataset.data[0];
console.log('📊 First point timestamp:', firstPoint.x);
console.log('📊 Current time:', Date.now());
console.log('📊 Time difference (sec):', (Date.now() - firstPoint.x) / 1000);
});
```
Si la diferencia de tiempo es muy grande (>60 segundos), el punto puede estar fuera de la ventana de tiempo.
### **Problema: Escala realtime no funciona**
**Causa:** La escala no se inicializó correctamente.
**Solución:**
```javascript
// Re-inicializar plot
const sessionId = 'plot_13'; // Cambiar por tu session ID
plotManager.controlPlot(sessionId, 'stop');
setTimeout(() => {
plotManager.controlPlot(sessionId, 'start');
}, 1000);
```
### **Problema: Data Points siempre en 0**
**Causa:** Los datos no se están agregando al chart o se eliminan inmediatamente.
**Solución verificar:**
1. **TTL Configuration**: Los datos pueden estar expirando muy rápido
2. **Timestamp Format**: Los timestamps pueden estar en formato incorrecto
3. **Dataset Index**: Los datos se pueden estar agregando al dataset incorrecto
```javascript
// Agregar punto de prueba manualmente
const sessionData = plotManager.sessions.get('plot_13');
if (sessionData?.chart) {
window.ChartStreaming.addStreamingData(sessionData.chart, 0, {
x: Date.now(),
y: Math.random() * 100
});
console.log('📈 Test point added');
}
```
## 🎯 Test de Resolución Rápida
**Ejecutar este script completo en consola:**
```javascript
// Test completo de diagnóstico
console.log('🔧 DIAGNÓSTICO COMPLETO');
console.log('='.repeat(50));
// 1. Verificar componentes básicos
console.log('1⃣ COMPONENTES:');
console.log('Chart.js:', typeof Chart !== 'undefined' ? '✅' : '❌');
console.log('ChartStreaming:', typeof window.ChartStreaming !== 'undefined' ? '✅' : '❌');
console.log('PlotManager:', typeof plotManager !== 'undefined' ? '✅' : '❌');
// 2. Verificar sesiones activas
if (plotManager && plotManager.sessions.size > 0) {
console.log('\n2⃣ SESIONES ACTIVAS:');
for (const [sessionId, sessionData] of plotManager.sessions) {
console.log(`📈 ${sessionId}:`, {
hasChart: !!sessionData.chart,
scaleType: sessionData.chart?.scales?.x?.type,
datasets: sessionData.chart?.data?.datasets?.length || 0,
dataPoints: sessionData.chart?.data?.datasets?.reduce((total, d) => total + (d.data?.length || 0), 0) || 0
});
}
}
// 3. Test de backend data
console.log('\n3⃣ BACKEND DATA TEST:');
if (plotManager && plotManager.sessions.size > 0) {
const firstSessionId = Array.from(plotManager.sessions.keys())[0];
fetch(`/api/plots/${firstSessionId}/data`)
.then(r => r.json())
.then(data => {
console.log('📊 Backend response:', {
success: !!data.datasets,
datasets: data.datasets?.length || 0,
totalPoints: data.data_points_count || 0,
firstDatasetPoints: data.datasets?.[0]?.data?.length || 0
});
})
.catch(err => console.log('❌ Backend error:', err.message));
}
console.log('\n4⃣ NEXT STEPS:');
console.log('- enablePlotDebug() para logs detallados');
console.log('- forceStreamingUpdate() para forzar actualización');
console.log('- Si persiste el problema, revisar configuración del backend');
```
## 📞 Contacto de Soporte
Si después de estos pasos el problema persiste:
1. **Compartir resultado completo** del diagnóstico en consola
2. **Verificar logs del backend** en la terminal donde corre `python main.py`
3. **Revisar Network tab** en DevTools para errores de red
---
## 🎉 Resultado Esperado
Cuando funcione correctamente verás:
```
📈 Chart.js Streaming Plugin loaded successfully
📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false}
📈 Plot plot_13: Successfully initialized 1 streaming datasets
📈 Plot plot_13: Fetching data from backend...
📈 Plot plot_13: Adding 3 new points for UR29_Brix
📈 Added point to dataset 0 (UR29_Brix): x=1642598234567, y=54.258
```
Y el gráfico mostrará:
- ✅ Línea de tiempo deslizándose automáticamente
- ✅ Data Points incrementándose
- ✅ Líneas de variables dibujándose en tiempo real
- ✅ Escala Y ajustándose a los datos

View File

@ -4119,8 +4119,473 @@
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-07-21T17:01:18.871713",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-07-21T17:01:18.903374",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T17:01:18.912924",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T17:13:36.924970",
"level": "info",
"event_type": "plot_session_removed",
"message": "Plot session 'Conducibilita Prodotto' removed",
"details": {
"session_id": "plot_9"
}
},
{
"timestamp": "2025-07-21T17:13:40.385435",
"level": "info",
"event_type": "plot_session_removed",
"message": "Plot session 'Brix' removed",
"details": {
"session_id": "plot_12"
}
},
{
"timestamp": "2025-07-21T17:13:59.375435",
"level": "info",
"event_type": "plot_session_created",
"message": "Plot session 'Brix' created and started",
"details": {
"session_id": "plot_13",
"variables": [
"UR29_Brix"
],
"time_window": 10,
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-07-21T17:33:27.303533",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-07-21T17:33:27.334173",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T17:33:27.345774",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T17:34:51.459774",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-07-21T17:34:51.489625",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T17:34:51.498003",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T17:38:38.361177",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-07-21T17:38:38.394439",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T17:38:38.402804",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T18:22:11.672676",
"level": "info",
"event_type": "csv_recording_stopped",
"message": "CSV recording stopped (dataset threads continue for UDP streaming)",
"details": {}
},
{
"timestamp": "2025-07-21T18:22:11.682630",
"level": "info",
"event_type": "udp_streaming_stopped",
"message": "UDP streaming to PlotJuggler stopped (CSV recording continues)",
"details": {}
},
{
"timestamp": "2025-07-21T18:22:11.818203",
"level": "info",
"event_type": "dataset_deactivated",
"message": "Dataset deactivated: DAR",
"details": {
"dataset_id": "dar"
}
},
{
"timestamp": "2025-07-21T18:22:11.827661",
"level": "info",
"event_type": "plc_disconnection",
"message": "Disconnected from PLC 10.1.33.11 (stopped recording and streaming)",
"details": {}
},
{
"timestamp": "2025-07-21T18:24:05.417266",
"level": "info",
"event_type": "plot_session_removed",
"message": "Plot session 'Brix' removed",
"details": {
"session_id": "plot_13"
}
},
{
"timestamp": "2025-07-21T18:25:07.058543",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T18:25:07.066931",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T18:25:07.076741",
"level": "info",
"event_type": "plc_connection",
"message": "Successfully connected to PLC 10.1.33.11 and auto-started CSV recording for 1 datasets",
"details": {
"ip": "10.1.33.11",
"rack": 0,
"slot": 2,
"auto_started_recording": true,
"recording_datasets": 1,
"dataset_names": [
"DAR"
]
}
},
{
"timestamp": "2025-07-21T18:25:18.499928",
"level": "info",
"event_type": "plot_session_created",
"message": "Plot session 'Brix' created and started",
"details": {
"session_id": "plot_14",
"variables": [
"UR29_Brix"
],
"time_window": 60,
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-07-21T18:26:03.568841",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-07-21T18:26:03.600360",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T18:26:03.609460",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T18:27:08.559092",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-07-21T18:27:08.586976",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T18:27:08.593701",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T18:27:09.538108",
"level": "info",
"event_type": "csv_recording_stopped",
"message": "CSV recording stopped (dataset threads continue for UDP streaming)",
"details": {}
},
{
"timestamp": "2025-07-21T18:27:09.545334",
"level": "info",
"event_type": "udp_streaming_stopped",
"message": "UDP streaming to PlotJuggler stopped (CSV recording continues)",
"details": {}
},
{
"timestamp": "2025-07-21T18:27:09.596088",
"level": "info",
"event_type": "dataset_deactivated",
"message": "Dataset deactivated: DAR",
"details": {
"dataset_id": "dar"
}
},
{
"timestamp": "2025-07-21T18:27:09.606944",
"level": "info",
"event_type": "plc_disconnection",
"message": "Disconnected from PLC 10.1.33.11 (stopped recording and streaming)",
"details": {}
},
{
"timestamp": "2025-07-21T18:27:12.713350",
"level": "info",
"event_type": "csv_recording_stopped",
"message": "CSV recording stopped (dataset threads continue for UDP streaming)",
"details": {}
},
{
"timestamp": "2025-07-21T18:27:12.727587",
"level": "info",
"event_type": "udp_streaming_stopped",
"message": "UDP streaming to PlotJuggler stopped (CSV recording continues)",
"details": {}
},
{
"timestamp": "2025-07-21T18:27:12.762044",
"level": "info",
"event_type": "plc_disconnection",
"message": "Disconnected from PLC 10.1.33.11 (stopped recording and streaming)",
"details": {}
},
{
"timestamp": "2025-07-21T18:27:26.860032",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T18:27:26.872811",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T18:27:26.884920",
"level": "info",
"event_type": "plc_connection",
"message": "Successfully connected to PLC 10.1.33.11 and auto-started CSV recording for 1 datasets",
"details": {
"ip": "10.1.33.11",
"rack": 0,
"slot": 2,
"auto_started_recording": true,
"recording_datasets": 1,
"dataset_names": [
"DAR"
]
}
},
{
"timestamp": "2025-07-21T18:34:08.696308",
"level": "info",
"event_type": "application_started",
"message": "Application initialization completed successfully",
"details": {}
},
{
"timestamp": "2025-07-21T18:34:08.730392",
"level": "info",
"event_type": "dataset_activated",
"message": "Dataset activated: DAR",
"details": {
"dataset_id": "dar",
"variables_count": 6,
"streaming_count": 4,
"prefix": "dar"
}
},
{
"timestamp": "2025-07-21T18:34:08.738627",
"level": "info",
"event_type": "csv_recording_started",
"message": "CSV recording started: 1 datasets activated",
"details": {
"activated_datasets": 1,
"total_datasets": 2
}
},
{
"timestamp": "2025-07-21T18:34:46.471786",
"level": "info",
"event_type": "plot_session_removed",
"message": "Plot session 'Brix' removed",
"details": {
"session_id": "plot_14"
}
},
{
"timestamp": "2025-07-21T18:35:13.057820",
"level": "info",
"event_type": "plot_session_created",
"message": "Plot session 'Brix' created and started",
"details": {
"session_id": "plot_15",
"variables": [
"UR29_Brix"
],
"time_window": 60,
"trigger_variable": null,
"auto_started": true
}
},
{
"timestamp": "2025-07-21T18:36:17.216085",
"level": "info",
"event_type": "plot_session_removed",
"message": "Plot session 'Brix' removed",
"details": {
"session_id": "plot_15"
}
},
{
"timestamp": "2025-07-21T18:38:00.731968",
"level": "info",
"event_type": "plot_session_created",
"message": "Plot session 'TEst' created and started",
"details": {
"session_id": "plot_16",
"variables": [
"UR29_Brix"
],
"time_window": 60,
"trigger_variable": null,
"auto_started": true
}
}
],
"last_updated": "2025-07-21T14:44:25.311128",
"total_entries": 389
"last_updated": "2025-07-21T18:38:00.731968",
"total_entries": 436
}

View File

@ -48,8 +48,8 @@
"streaming_variables": [
"UR29_Brix_Digital",
"UR62_Brix",
"UR29_Brix",
"CTS306_PV"
"CTS306_PV",
"UR29_Brix"
],
"sampling_interval": 0.2,
"enabled": true,
@ -70,5 +70,5 @@
],
"current_dataset_id": "dar",
"version": "1.0",
"last_update": "2025-07-21T14:43:41.517456"
"last_update": "2025-07-21T18:34:08.728102"
}

View File

@ -1,34 +1,20 @@
{
"plots": {
"plot_9": {
"name": "Conducibilita Prodotto",
"plot_16": {
"name": "TEst",
"variables": [
"UR29_Brix"
],
"time_window": 10,
"y_min": null,
"y_max": null,
"trigger_variable": null,
"trigger_enabled": false,
"trigger_on_true": true,
"session_id": "plot_9"
},
"plot_12": {
"name": "Brix",
"variables": [
"UR29_Brix",
"UR62_Brix"
],
"time_window": 60,
"y_min": null,
"y_max": null,
"trigger_variable": null,
"trigger_enabled": false,
"trigger_on_true": true,
"session_id": "plot_12"
"session_id": "plot_16"
}
},
"session_counter": 13,
"last_saved": "2025-07-21T14:44:25.311128",
"session_counter": 17,
"last_saved": "2025-07-21T18:38:00.730994",
"version": "1.0"
}

View File

@ -0,0 +1,405 @@
/**
* 📈 Chart.js Plugin Streaming Integration
* Integra chartjs-plugin-streaming para plotting en tiempo real
*
* Combinación de módulos:
* - helpers.streaming.js
* - scale.realtime.js
* - plugin.streaming.js
* - plugin.zoom.js (integración con zoom)
*/
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
factory(exports, require('chart.js'));
} else if (typeof define === 'function' && define.amd) {
define(['exports', 'chart.js'], factory);
} else {
global = global || self;
factory(global.ChartStreaming = {}, global.Chart);
}
})(this, function (exports, Chart) {
'use strict';
// ============= HELPERS.STREAMING.JS =============
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function resolveOption(scale, key) {
const realtimeOptions = scale.options.realtime || {};
const scaleOptions = scale.options;
if (realtimeOptions[key] !== undefined) {
return realtimeOptions[key];
}
if (scaleOptions[key] !== undefined) {
return scaleOptions[key];
}
// Valores por defecto
const defaults = {
duration: 10000,
delay: 0,
refresh: 1000,
frameRate: 30,
pause: false,
ttl: undefined,
onRefresh: null
};
return defaults[key];
}
function getAxisMap(element, keys, meta) {
const axis = meta.vAxisID || 'y';
return keys[axis] || [];
}
// ============= SCALE.REALTIME.JS (Corregido) =============
class RealTimeScale extends Chart.Scale {
constructor(cfg) {
super(cfg);
this.type = 'realtime';
}
init(scaleOptions, scaleContext) {
super.init(scaleOptions, scaleContext);
const me = this;
const chart = me.chart;
const streaming = chart.$streaming = chart.$streaming || {};
streaming.enabled = true; // Marcar como streaming activo
// 🔧 DEBUG: Ver qué opciones estamos recibiendo
console.log('📈 RealTimeScale DEBUG - scaleOptions:', scaleOptions);
console.log('📈 RealTimeScale DEBUG - me.options:', me.options);
console.log('📈 RealTimeScale DEBUG - me.options.realtime:', me.options.realtime);
// Inicializar opciones de tiempo real
const onRefreshResolved = resolveOption(me, 'onRefresh');
console.log('📈 RealTimeScale DEBUG - onRefresh resolved:', onRefreshResolved, typeof onRefreshResolved);
me.realtime = {
duration: resolveOption(me, 'duration'),
delay: resolveOption(me, 'delay'),
refresh: resolveOption(me, 'refresh'),
frameRate: resolveOption(me, 'frameRate'),
pause: resolveOption(me, 'pause'),
ttl: resolveOption(me, 'ttl'),
onRefresh: onRefreshResolved
};
console.log('📈 RealTimeScale initialized:', {
duration: me.realtime.duration,
refresh: me.realtime.refresh,
pause: me.realtime.pause,
hasOnRefresh: typeof me.realtime.onRefresh === 'function'
});
// Configurar intervalo de actualización
if (!streaming.intervalId && me.realtime.refresh > 0) {
streaming.intervalId = setInterval(() => {
if (!me.realtime.pause && typeof me.realtime.onRefresh === 'function') {
me.realtime.onRefresh(chart);
}
me.updateRealTimeData();
chart.update('quiet');
}, me.realtime.refresh);
console.log('📈 RealTimeScale interval started:', me.realtime.refresh + 'ms');
}
}
updateRealTimeData() {
const me = this;
const chart = me.chart;
if (!chart.data || !chart.data.datasets) {
return;
}
const now = Date.now();
const duration = me.realtime.duration;
const delay = me.realtime.delay;
const ttl = me.realtime.ttl || duration * 2; // TTL por defecto
// Calcular ventana de tiempo
me.max = now - delay;
me.min = me.max - duration;
// Limpiar datos antiguos automáticamente
const cutoff = now - ttl;
chart.data.datasets.forEach(dataset => {
if (dataset.data) {
const oldLength = dataset.data.length;
dataset.data = dataset.data.filter(point => point.x > cutoff);
if (oldLength !== dataset.data.length) {
console.log(`📈 Cleaned ${oldLength - dataset.data.length} old points from ${dataset.label}`);
}
}
});
}
update(args) {
this.updateRealTimeData();
super.update(args);
}
destroy() {
const me = this;
const chart = me.chart;
const streaming = chart.$streaming;
if (streaming && streaming.intervalId) {
clearInterval(streaming.intervalId);
delete streaming.intervalId;
console.log('📈 RealTimeScale interval cleared');
}
super.destroy();
}
static id = 'realtime';
static defaults = {
realtime: {
duration: 10000,
delay: 0,
refresh: 1000,
frameRate: 30,
pause: false,
ttl: undefined,
onRefresh: null
},
time: {
unit: 'second',
displayFormats: {
second: 'HH:mm:ss'
}
}
};
}
// ============= PLUGIN.STREAMING.JS (Simplificado) =============
const streamingPlugin = {
id: 'streaming',
beforeInit(chart) {
const streaming = chart.$streaming = chart.$streaming || {};
streaming.enabled = false;
// Detectar si hay escalas realtime
const scales = chart.options.scales || {};
Object.keys(scales).forEach(scaleId => {
if (scales[scaleId].type === 'realtime') {
streaming.enabled = true;
}
});
},
afterInit(chart) {
const streaming = chart.$streaming;
if (streaming && streaming.enabled) {
// Configurar actualización automática
const update = chart.update;
chart.update = function (mode) {
if (mode === 'quiet') {
// Actualización silenciosa para streaming
Chart.prototype.update.call(this, mode);
} else {
update.call(this, mode);
}
};
}
},
beforeUpdate(chart) {
const streaming = chart.$streaming;
if (!streaming || !streaming.enabled) return;
// Permitir que las líneas Bézier se extiendan fuera del área del gráfico
const elements = chart.options.elements || {};
if (elements.line) {
elements.line.capBezierPoints = false;
}
},
destroy(chart) {
const streaming = chart.$streaming;
if (streaming && streaming.intervalId) {
clearInterval(streaming.intervalId);
delete streaming.intervalId;
}
delete chart.$streaming;
}
};
// ============= REGISTRO DE COMPONENTES =============
// Registrar escala realtime
Chart.register(RealTimeScale);
// Registrar plugin de streaming
Chart.register(streamingPlugin);
// ============= UTILIDADES PARA LA APLICACIÓN =============
/**
* Crea una configuración de Chart.js optimizada para streaming
*/
function createStreamingChartConfig(options = {}) {
const config = {
type: 'line',
data: {
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false, // Desactivar animaciones para mejor performance
scales: {
x: {
type: 'realtime',
realtime: {
duration: options.duration || 60000, // 60 segundos por defecto
delay: options.delay || 0,
refresh: options.refresh || 1000, // 1 segundo
frameRate: options.frameRate || 30,
pause: options.pause || false,
ttl: options.ttl || undefined,
onRefresh: options.onRefresh || null
},
title: {
display: true,
text: 'Tiempo'
}
},
y: {
title: {
display: true,
text: 'Valor'
},
min: options.yMin,
max: options.yMax
}
},
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
},
elements: {
point: {
radius: 0, // Sin puntos para mejor performance
hoverRadius: 3
},
line: {
tension: 0.1,
borderWidth: 2
}
}
},
plugins: ['streaming']
};
return config;
}
/**
* Agrega datos a un dataset de streaming
*/
function addStreamingData(chart, datasetIndex, data) {
if (!chart || !chart.data || !chart.data.datasets[datasetIndex]) {
console.warn(`📈 Cannot add streaming data - chart or dataset ${datasetIndex} not found`);
return false;
}
const dataset = chart.data.datasets[datasetIndex];
if (!dataset.data) {
dataset.data = [];
}
// Agregar nuevo punto con timestamp
const timestamp = data.x || Date.now();
const newPoint = {
x: timestamp,
y: data.y
};
dataset.data.push(newPoint);
console.log(`📈 Added point to dataset ${datasetIndex} (${dataset.label}): x=${timestamp}, y=${data.y}`);
// Chart.js se encarga automáticamente de eliminar datos antiguos
// basado en la configuración de TTL y duration de la escala realtime
return true;
}
/**
* Controla la pausa/reanudación del streaming
*/
function setStreamingPause(chart, paused) {
if (!chart || !chart.$streaming) return;
const scales = chart.scales;
Object.keys(scales).forEach(scaleId => {
const scale = scales[scaleId];
if (scale instanceof RealTimeScale) {
scale.realtime.pause = paused;
}
});
}
/**
* Limpia todos los datos de streaming
*/
function clearStreamingData(chart) {
if (!chart || !chart.data) return;
chart.data.datasets.forEach(dataset => {
if (dataset.data) {
dataset.data.length = 0;
}
});
chart.update('quiet');
}
// ============= EXPORTS =============
// Exportar para uso en la aplicación
exports.RealTimeScale = RealTimeScale;
exports.streamingPlugin = streamingPlugin;
exports.createStreamingChartConfig = createStreamingChartConfig;
exports.addStreamingData = addStreamingData;
exports.setStreamingPause = setStreamingPause;
exports.clearStreamingData = clearStreamingData;
// Hacer disponible globalmente
if (typeof window !== 'undefined') {
window.ChartStreaming = {
createStreamingChartConfig,
addStreamingData,
setStreamingPause,
clearStreamingData,
RealTimeScale,
streamingPlugin
};
}
console.log('📈 Chart.js Streaming Plugin loaded successfully');
});

View File

@ -0,0 +1,85 @@
import {callback as call, each, noop, requestAnimFrame, valueOrDefault} from 'chart.js/helpers';
export function clamp(value, lower, upper) {
return Math.min(Math.max(value, lower), upper);
}
export function resolveOption(scale, key) {
const realtimeOpts = scale.options.realtime;
const streamingOpts = scale.chart.options.plugins.streaming;
return valueOrDefault(realtimeOpts[key], streamingOpts[key]);
}
export function getAxisMap(element, {x, y}, {xAxisID, yAxisID}) {
const axisMap = {};
each(x, key => {
axisMap[key] = {axisId: xAxisID};
});
each(y, key => {
axisMap[key] = {axisId: yAxisID};
});
return axisMap;
}
/**
* Cancel animation polyfill
*/
const cancelAnimFrame = (function() {
if (typeof window === 'undefined') {
return noop;
}
return window.cancelAnimationFrame;
}());
export function startFrameRefreshTimer(context, func) {
if (!context.frameRequestID) {
const refresh = () => {
const nextRefresh = context.nextRefresh || 0;
const now = Date.now();
if (nextRefresh <= now) {
const newFrameRate = call(func);
const frameDuration = 1000 / (Math.max(newFrameRate, 0) || 30);
const newNextRefresh = context.nextRefresh + frameDuration || 0;
context.nextRefresh = newNextRefresh > now ? newNextRefresh : now + frameDuration;
}
context.frameRequestID = requestAnimFrame.call(window, refresh);
};
context.frameRequestID = requestAnimFrame.call(window, refresh);
}
}
export function stopFrameRefreshTimer(context) {
const frameRequestID = context.frameRequestID;
if (frameRequestID) {
cancelAnimFrame.call(window, frameRequestID);
delete context.frameRequestID;
}
}
export function stopDataRefreshTimer(context) {
const refreshTimerID = context.refreshTimerID;
if (refreshTimerID) {
clearInterval(refreshTimerID);
delete context.refreshTimerID;
delete context.refreshInterval;
}
}
export function startDataRefreshTimer(context, func, interval) {
if (!context.refreshTimerID) {
context.refreshTimerID = setInterval(() => {
const newInterval = call(func);
if (context.refreshInterval !== newInterval && !isNaN(newInterval)) {
stopDataRefreshTimer(context);
startDataRefreshTimer(context, func, newInterval);
}
}, interval || 0);
context.refreshInterval = interval || 0;
}
}

View File

@ -0,0 +1,216 @@
import {Chart, DatasetController, defaults, registry} from 'chart.js';
import {each, noop, getRelativePosition, clipArea, unclipArea} from 'chart.js/helpers';
import {getAxisMap} from '../helpers/helpers.streaming';
import {attachChart as annotationAttachChart, detachChart as annotationDetachChart} from '../plugins/plugin.annotation';
import {update as tooltipUpdate} from '../plugins/plugin.tooltip';
import {attachChart as zoomAttachChart, detachChart as zoomDetachChart} from '../plugins/plugin.zoom';
import RealTimeScale from '../scales/scale.realtime';
import {version} from '../../package.json';
defaults.set('transitions', {
quiet: {
animation: {
duration: 0
}
}
});
const transitionKeys = {x: ['x', 'cp1x', 'cp2x'], y: ['y', 'cp1y', 'cp2y']};
function update(mode) {
const me = this;
if (mode === 'quiet') {
each(me.data.datasets, (dataset, datasetIndex) => {
const controller = me.getDatasetMeta(datasetIndex).controller;
// Set transition mode to 'quiet'
controller._setStyle = function(element, index, _mode, active) {
DatasetController.prototype._setStyle.call(this, element, index, 'quiet', active);
};
});
}
Chart.prototype.update.call(me, mode);
if (mode === 'quiet') {
each(me.data.datasets, (dataset, datasetIndex) => {
delete me.getDatasetMeta(datasetIndex).controller._setStyle;
});
}
}
function render(chart) {
const streaming = chart.$streaming;
chart.render();
if (streaming.lastMouseEvent) {
setTimeout(() => {
const lastMouseEvent = streaming.lastMouseEvent;
if (lastMouseEvent) {
chart._eventHandler(lastMouseEvent);
}
}, 0);
}
}
export default {
id: 'streaming',
version,
beforeInit(chart) {
const streaming = chart.$streaming = chart.$streaming || {render};
const canvas = streaming.canvas = chart.canvas;
const mouseEventListener = streaming.mouseEventListener = event => {
const pos = getRelativePosition(event, chart);
streaming.lastMouseEvent = {
type: 'mousemove',
chart: chart,
native: event,
x: pos.x,
y: pos.y
};
};
canvas.addEventListener('mousedown', mouseEventListener);
canvas.addEventListener('mouseup', mouseEventListener);
},
afterInit(chart) {
chart.update = update;
},
beforeUpdate(chart) {
const {scales, elements} = chart.options;
const tooltip = chart.tooltip;
each(scales, ({type}) => {
if (type === 'realtime') {
// Allow Bézier control to be outside the chart
elements.line.capBezierPoints = false;
}
});
if (tooltip) {
tooltip.update = tooltipUpdate;
}
try {
const plugin = registry.getPlugin('annotation');
annotationAttachChart(plugin, chart);
} catch (e) {
annotationDetachChart(chart);
}
try {
const plugin = registry.getPlugin('zoom');
zoomAttachChart(plugin, chart);
} catch (e) {
zoomDetachChart(chart);
}
},
beforeDatasetUpdate(chart, args) {
const {meta, mode} = args;
if (mode === 'quiet') {
const {controller, $animations} = meta;
// Skip updating element options if show/hide transition is active
if ($animations && $animations.visible && $animations.visible._active) {
controller.updateElement = noop;
controller.updateSharedOptions = noop;
}
}
},
afterDatasetUpdate(chart, args) {
const {meta, mode} = args;
const {data: elements = [], dataset: element, controller} = meta;
for (let i = 0, ilen = elements.length; i < ilen; ++i) {
elements[i].$streaming = getAxisMap(elements[i], transitionKeys, meta);
}
if (element) {
element.$streaming = getAxisMap(element, transitionKeys, meta);
}
if (mode === 'quiet') {
delete controller.updateElement;
delete controller.updateSharedOptions;
}
},
beforeDatasetDraw(chart, args) {
const {ctx, chartArea, width, height} = chart;
const {xAxisID, yAxisID, controller} = args.meta;
const area = {
left: 0,
top: 0,
right: width,
bottom: height
};
if (xAxisID && controller.getScaleForId(xAxisID) instanceof RealTimeScale) {
area.left = chartArea.left;
area.right = chartArea.right;
}
if (yAxisID && controller.getScaleForId(yAxisID) instanceof RealTimeScale) {
area.top = chartArea.top;
area.bottom = chartArea.bottom;
}
clipArea(ctx, area);
},
afterDatasetDraw(chart) {
unclipArea(chart.ctx);
},
beforeEvent(chart, args) {
const streaming = chart.$streaming;
const event = args.event;
if (event.type === 'mousemove') {
// Save mousemove event for reuse
streaming.lastMouseEvent = event;
} else if (event.type === 'mouseout') {
// Remove mousemove event
delete streaming.lastMouseEvent;
}
},
destroy(chart) {
const {scales, $streaming: streaming, tooltip} = chart;
const {canvas, mouseEventListener} = streaming;
delete chart.update;
if (tooltip) {
delete tooltip.update;
}
canvas.removeEventListener('mousedown', mouseEventListener);
canvas.removeEventListener('mouseup', mouseEventListener);
each(scales, scale => {
if (scale instanceof RealTimeScale) {
scale.destroy();
}
});
},
defaults: {
duration: 10000,
delay: 0,
frameRate: 30,
refresh: 1000,
onRefresh: null,
pause: false,
ttl: undefined
},
descriptors: {
_scriptable: name => name !== 'onRefresh'
}
};

View File

@ -0,0 +1,125 @@
import {each} from 'chart.js/helpers';
import {clamp, resolveOption} from '../helpers/helpers.streaming';
const chartStates = new WeakMap();
function getState(chart) {
let state = chartStates.get(chart);
if (!state) {
state = {originalScaleOptions: {}};
chartStates.set(chart, state);
}
return state;
}
function removeState(chart) {
chartStates.delete(chart);
}
function storeOriginalScaleOptions(chart) {
const {originalScaleOptions} = getState(chart);
const scales = chart.scales;
each(scales, scale => {
const id = scale.id;
if (!originalScaleOptions[id]) {
originalScaleOptions[id] = {
duration: resolveOption(scale, 'duration'),
delay: resolveOption(scale, 'delay')
};
}
});
each(originalScaleOptions, (opt, key) => {
if (!scales[key]) {
delete originalScaleOptions[key];
}
});
return originalScaleOptions;
}
function zoomRealTimeScale(scale, zoom, center, limits) {
const {chart, axis} = scale;
const {minDuration = 0, maxDuration = Infinity, minDelay = -Infinity, maxDelay = Infinity} = limits && limits[axis] || {};
const realtimeOpts = scale.options.realtime;
const duration = resolveOption(scale, 'duration');
const delay = resolveOption(scale, 'delay');
const newDuration = clamp(duration * (2 - zoom), minDuration, maxDuration);
let maxPercent, newDelay;
storeOriginalScaleOptions(chart);
if (scale.isHorizontal()) {
maxPercent = (scale.right - center.x) / (scale.right - scale.left);
} else {
maxPercent = (scale.bottom - center.y) / (scale.bottom - scale.top);
}
newDelay = delay + maxPercent * (duration - newDuration);
realtimeOpts.duration = newDuration;
realtimeOpts.delay = clamp(newDelay, minDelay, maxDelay);
return newDuration !== scale.max - scale.min;
}
function panRealTimeScale(scale, delta, limits) {
const {chart, axis} = scale;
const {minDelay = -Infinity, maxDelay = Infinity} = limits && limits[axis] || {};
const delay = resolveOption(scale, 'delay');
const newDelay = delay + (scale.getValueForPixel(delta) - scale.getValueForPixel(0));
storeOriginalScaleOptions(chart);
scale.options.realtime.delay = clamp(newDelay, minDelay, maxDelay);
return true;
}
function resetRealTimeScaleOptions(chart) {
const originalScaleOptions = storeOriginalScaleOptions(chart);
each(chart.scales, scale => {
const realtimeOptions = scale.options.realtime;
if (realtimeOptions) {
const original = originalScaleOptions[scale.id];
if (original) {
realtimeOptions.duration = original.duration;
realtimeOptions.delay = original.delay;
} else {
delete realtimeOptions.duration;
delete realtimeOptions.delay;
}
}
});
}
function initZoomPlugin(plugin) {
plugin.zoomFunctions.realtime = zoomRealTimeScale;
plugin.panFunctions.realtime = panRealTimeScale;
}
export function attachChart(plugin, chart) {
const streaming = chart.$streaming;
if (streaming.zoomPlugin !== plugin) {
const resetZoom = streaming.resetZoom = chart.resetZoom;
initZoomPlugin(plugin);
chart.resetZoom = transition => {
resetRealTimeScaleOptions(chart);
resetZoom(transition);
};
streaming.zoomPlugin = plugin;
}
}
export function detachChart(chart) {
const streaming = chart.$streaming;
if (streaming.zoomPlugin) {
chart.resetZoom = streaming.resetZoom;
removeState(chart);
delete streaming.resetZoom;
delete streaming.zoomPlugin;
}
}

View File

@ -0,0 +1,507 @@
import {defaults, TimeScale} from 'chart.js';
import {_lookup, callback as call, each, isArray, isFinite, isNumber, noop, clipArea, unclipArea} from 'chart.js/helpers';
import {resolveOption, startFrameRefreshTimer, stopFrameRefreshTimer, startDataRefreshTimer, stopDataRefreshTimer} from '../helpers/helpers.streaming';
import {getElements} from '../plugins/plugin.annotation';
// Ported from Chart.js 2.8.0 35273ee.
const INTERVALS = {
millisecond: {
common: true,
size: 1,
steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
},
second: {
common: true,
size: 1000,
steps: [1, 2, 5, 10, 15, 30]
},
minute: {
common: true,
size: 60000,
steps: [1, 2, 5, 10, 15, 30]
},
hour: {
common: true,
size: 3600000,
steps: [1, 2, 3, 6, 12]
},
day: {
common: true,
size: 86400000,
steps: [1, 2, 5]
},
week: {
common: false,
size: 604800000,
steps: [1, 2, 3, 4]
},
month: {
common: true,
size: 2.628e9,
steps: [1, 2, 3]
},
quarter: {
common: false,
size: 7.884e9,
steps: [1, 2, 3, 4]
},
year: {
common: true,
size: 3.154e10
}
};
// Ported from Chart.js 2.8.0 35273ee.
const UNITS = Object.keys(INTERVALS);
// Ported from Chart.js 2.8.0 35273ee.
function determineStepSize(min, max, unit, capacity) {
const range = max - min;
const {size: milliseconds, steps} = INTERVALS[unit];
let factor;
if (!steps) {
return Math.ceil(range / (capacity * milliseconds));
}
for (let i = 0, ilen = steps.length; i < ilen; ++i) {
factor = steps[i];
if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
break;
}
}
return factor;
}
// Ported from Chart.js 2.8.0 35273ee.
function determineUnitForAutoTicks(minUnit, min, max, capacity) {
const range = max - min;
const ilen = UNITS.length;
for (let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
const {common, size, steps} = INTERVALS[UNITS[i]];
const factor = steps ? steps[steps.length - 1] : Number.MAX_SAFE_INTEGER;
if (common && Math.ceil(range / (factor * size)) <= capacity) {
return UNITS[i];
}
}
return UNITS[ilen - 1];
}
// Ported from Chart.js 2.8.0 35273ee.
function determineMajorUnit(unit) {
for (let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
if (INTERVALS[UNITS[i]].common) {
return UNITS[i];
}
}
}
// Ported from Chart.js 3.2.0 e1404ac.
function addTick(ticks, time, timestamps) {
if (!timestamps) {
ticks[time] = true;
} else if (timestamps.length) {
const {lo, hi} = _lookup(timestamps, time);
const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
ticks[timestamp] = true;
}
}
const datasetPropertyKeys = [
'pointBackgroundColor',
'pointBorderColor',
'pointBorderWidth',
'pointRadius',
'pointRotation',
'pointStyle',
'pointHitRadius',
'pointHoverBackgroundColor',
'pointHoverBorderColor',
'pointHoverBorderWidth',
'pointHoverRadius',
'backgroundColor',
'borderColor',
'borderSkipped',
'borderWidth',
'hoverBackgroundColor',
'hoverBorderColor',
'hoverBorderWidth',
'hoverRadius',
'hitRadius',
'radius',
'rotation'
];
function clean(scale) {
const {chart, id, max} = scale;
const duration = resolveOption(scale, 'duration');
const delay = resolveOption(scale, 'delay');
const ttl = resolveOption(scale, 'ttl');
const pause = resolveOption(scale, 'pause');
const min = Date.now() - (isNaN(ttl) ? duration + delay : ttl);
let i, start, count, removalRange;
// Remove old data
each(chart.data.datasets, (dataset, datasetIndex) => {
const meta = chart.getDatasetMeta(datasetIndex);
const axis = id === meta.xAxisID && 'x' || id === meta.yAxisID && 'y';
if (axis) {
const controller = meta.controller;
const data = dataset.data;
const length = data.length;
if (pause) {
// If the scale is paused, preserve the visible data points
for (i = 0; i < length; ++i) {
const point = controller.getParsed(i);
if (point && !(point[axis] < max)) {
break;
}
}
start = i + 2;
} else {
start = 0;
}
for (i = start; i < length; ++i) {
const point = controller.getParsed(i);
if (!point || !(point[axis] <= min)) {
break;
}
}
count = i - start;
if (isNaN(ttl)) {
// Keep the last two data points outside the range not to affect the existing bezier curve
count = Math.max(count - 2, 0);
}
data.splice(start, count);
each(datasetPropertyKeys, key => {
if (isArray(dataset[key])) {
dataset[key].splice(start, count);
}
});
each(dataset.datalabels, value => {
if (isArray(value)) {
value.splice(start, count);
}
});
if (typeof data[0] !== 'object') {
removalRange = {
start: start,
count: count
};
}
each(chart._active, (item, index) => {
if (item.datasetIndex === datasetIndex && item.index >= start) {
if (item.index >= start + count) {
item.index -= count;
} else {
chart._active.splice(index, 1);
}
}
}, null, true);
}
});
if (removalRange) {
chart.data.labels.splice(removalRange.start, removalRange.count);
}
}
function transition(element, id, translate) {
const animations = element.$animations || {};
each(element.$streaming, (item, key) => {
if (item.axisId === id) {
const delta = item.reverse ? -translate : translate;
const animation = animations[key];
if (isFinite(element[key])) {
element[key] -= delta;
}
if (animation) {
animation._from -= delta;
animation._to -= delta;
}
}
});
}
function scroll(scale) {
const {chart, id, $realtime: realtime} = scale;
const duration = resolveOption(scale, 'duration');
const delay = resolveOption(scale, 'delay');
const isHorizontal = scale.isHorizontal();
const length = isHorizontal ? scale.width : scale.height;
const now = Date.now();
const tooltip = chart.tooltip;
const annotations = getElements(chart);
let offset = length * (now - realtime.head) / duration;
if (isHorizontal === !!scale.options.reverse) {
offset = -offset;
}
// Shift all the elements leftward or downward
each(chart.data.datasets, (dataset, datasetIndex) => {
const meta = chart.getDatasetMeta(datasetIndex);
const {data: elements = [], dataset: element} = meta;
for (let i = 0, ilen = elements.length; i < ilen; ++i) {
transition(elements[i], id, offset);
}
if (element) {
transition(element, id, offset);
delete element._path;
}
});
// Shift all the annotation elements leftward or downward
for (let i = 0, ilen = annotations.length; i < ilen; ++i) {
transition(annotations[i], id, offset);
}
// Shift tooltip leftward or downward
if (tooltip) {
transition(tooltip, id, offset);
}
scale.max = now - delay;
scale.min = scale.max - duration;
realtime.head = now;
}
export default class RealTimeScale extends TimeScale {
constructor(props) {
super(props);
this.$realtime = this.$realtime || {};
}
init(scaleOpts, opts) {
const me = this;
super.init(scaleOpts, opts);
startDataRefreshTimer(me.$realtime, () => {
const chart = me.chart;
const onRefresh = resolveOption(me, 'onRefresh');
call(onRefresh, [chart], me);
clean(me);
chart.update('quiet');
return resolveOption(me, 'refresh');
});
}
update(maxWidth, maxHeight, margins) {
const me = this;
const {$realtime: realtime, options} = me;
const {bounds, offset, ticks: ticksOpts} = options;
const {autoSkip, source, major: majorTicksOpts} = ticksOpts;
const majorEnabled = majorTicksOpts.enabled;
if (resolveOption(me, 'pause')) {
stopFrameRefreshTimer(realtime);
} else {
if (!realtime.frameRequestID) {
realtime.head = Date.now();
}
startFrameRefreshTimer(realtime, () => {
const chart = me.chart;
const streaming = chart.$streaming;
scroll(me);
if (streaming) {
call(streaming.render, [chart]);
}
return resolveOption(me, 'frameRate');
});
}
options.bounds = undefined;
options.offset = false;
ticksOpts.autoSkip = false;
ticksOpts.source = source === 'auto' ? '' : source;
majorTicksOpts.enabled = true;
super.update(maxWidth, maxHeight, margins);
options.bounds = bounds;
options.offset = offset;
ticksOpts.autoSkip = autoSkip;
ticksOpts.source = source;
majorTicksOpts.enabled = majorEnabled;
}
buildTicks() {
const me = this;
const duration = resolveOption(me, 'duration');
const delay = resolveOption(me, 'delay');
const max = me.$realtime.head - delay;
const min = max - duration;
const maxArray = [1e15, max];
const minArray = [-1e15, min];
Object.defineProperty(me, 'min', {
get: () => minArray.shift(),
set: noop
});
Object.defineProperty(me, 'max', {
get: () => maxArray.shift(),
set: noop
});
const ticks = super.buildTicks();
delete me.min;
delete me.max;
me.min = min;
me.max = max;
return ticks;
}
calculateLabelRotation() {
const ticksOpts = this.options.ticks;
const maxRotation = ticksOpts.maxRotation;
ticksOpts.maxRotation = ticksOpts.minRotation || 0;
super.calculateLabelRotation();
ticksOpts.maxRotation = maxRotation;
}
fit() {
const me = this;
const options = me.options;
super.fit();
if (options.ticks.display && options.display && me.isHorizontal()) {
me.paddingLeft = 3;
me.paddingRight = 3;
me._handleMargins();
}
}
draw(chartArea) {
const me = this;
const {chart, ctx} = me;
const area = me.isHorizontal() ?
{
left: chartArea.left,
top: 0,
right: chartArea.right,
bottom: chart.height
} : {
left: 0,
top: chartArea.top,
right: chart.width,
bottom: chartArea.bottom
};
me._gridLineItems = null;
me._labelItems = null;
// Clip and draw the scale
clipArea(ctx, area);
super.draw(chartArea);
unclipArea(ctx);
}
destroy() {
const realtime = this.$realtime;
stopFrameRefreshTimer(realtime);
stopDataRefreshTimer(realtime);
}
_generate() {
const me = this;
const adapter = me._adapter;
const duration = resolveOption(me, 'duration');
const delay = resolveOption(me, 'delay');
const refresh = resolveOption(me, 'refresh');
const max = me.$realtime.head - delay;
const min = max - duration;
const capacity = me._getLabelCapacity(min);
const {time: timeOpts, ticks: ticksOpts} = me.options;
const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
const major = determineMajorUnit(minor);
const stepSize = timeOpts.stepSize || determineStepSize(min, max, minor, capacity);
const weekday = minor === 'week' ? timeOpts.isoWeekday : false;
const majorTicksEnabled = ticksOpts.major.enabled;
const hasWeekday = isNumber(weekday) || weekday === true;
const interval = INTERVALS[minor];
const ticks = {};
let first = min;
let time, count;
// For 'week' unit, handle the first day of week option
if (hasWeekday) {
first = +adapter.startOf(first, 'isoWeek', weekday);
}
// Align first ticks on unit
first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
// Prevent browser from freezing in case user options request millions of milliseconds
if (adapter.diff(max, min, minor) > 100000 * stepSize) {
throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
}
time = first;
if (majorTicksEnabled && major && !hasWeekday && !timeOpts.round) {
// Align the first tick on the previous `minor` unit aligned on the `major` unit:
// we first aligned time on the previous `major` unit then add the number of full
// stepSize there is between first and the previous major time.
time = +adapter.startOf(time, major);
time = +adapter.add(time, ~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
}
const timestamps = ticksOpts.source === 'data' && me.getDataTimestamps();
for (count = 0; time < max + refresh; time = +adapter.add(time, stepSize, minor), count++) {
addTick(ticks, time, timestamps);
}
if (time === max + refresh || count === 1) {
addTick(ticks, time, timestamps);
}
return Object.keys(ticks).sort((a, b) => a - b).map(x => +x);
}
}
RealTimeScale.id = 'realtime';
RealTimeScale.defaults = {
bounds: 'data',
adapters: {},
time: {
parser: false, // false == a pattern string from or a custom callback that converts its argument to a timestamp
unit: false, // false == automatic or override with week, month, year, etc.
round: false, // none, or override with week, month, year, etc.
isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
minUnit: 'millisecond',
displayFormats: {}
},
realtime: {},
ticks: {
autoSkip: false,
source: 'auto',
major: {
enabled: true
}
}
};
defaults.describe('scale.realtime', {
_scriptable: name => name !== 'onRefresh'
});

View File

@ -0,0 +1,387 @@
/**
* 🔧 Script de Diagnóstico Detallado para Chart.js Streaming
* Ejecutar en consola del navegador para identificar el problema exacto
*/
// Función principal de diagnóstico
window.diagnoseStreamingIssue = async function () {
console.log('🔧 DIAGNÓSTICO DETALLADO DE STREAMING');
console.log('='.repeat(60));
const results = {
librariesLoaded: false,
plotManagerReady: false,
sessionExists: false,
backendData: null,
chartConfig: null,
streamingWorking: false,
errors: []
};
try {
// 1. Verificar librerías básicas
console.log('\n1⃣ VERIFICANDO LIBRERÍAS...');
results.librariesLoaded = {
chartjs: typeof Chart !== 'undefined',
chartStreaming: typeof window.ChartStreaming !== 'undefined',
plotManager: typeof plotManager !== 'undefined'
};
console.log('Chart.js:', results.librariesLoaded.chartjs ? '✅' : '❌');
console.log('ChartStreaming:', results.librariesLoaded.chartStreaming ? '✅' : '❌');
console.log('PlotManager:', results.librariesLoaded.plotManager ? '✅' : '❌');
if (!results.librariesLoaded.chartStreaming) {
results.errors.push('ChartStreaming plugin no está cargado');
console.error('❌ ChartStreaming plugin no está disponible');
return results;
}
// 2. Verificar PlotManager
console.log('\n2⃣ VERIFICANDO PLOT MANAGER...');
const pm = window.plotManager || plotManager;
if (pm && pm.sessions) {
results.plotManagerReady = true;
console.log('✅ PlotManager inicializado');
console.log('📊 Sesiones activas:', pm.sessions.size);
if (pm.sessions.size === 0) {
console.log('⚠️ No hay sesiones de plot activas');
console.log('💡 Crea un nuevo plot para continuar el diagnóstico');
return results;
}
} else {
results.errors.push('PlotManager no está inicializado');
console.error('❌ PlotManager no está disponible');
console.log('💡 Tip: Espera unos segundos e intenta de nuevo, plotManager podría estar inicializándose');
return results;
}
// 3. Verificar sesión específica
console.log('\n3⃣ ANALIZANDO SESIÓN DE PLOT...');
const sessionId = Array.from(pm.sessions.keys())[0];
const sessionData = pm.sessions.get(sessionId);
if (sessionData) {
results.sessionExists = true;
console.log(`📈 Sesión encontrada: ${sessionId}`);
// Verificar configuración del chart
results.chartConfig = {
hasChart: !!sessionData.chart,
chartType: sessionData.chart?.config?.type,
scaleType: sessionData.chart?.scales?.x?.type,
scaleConstructor: sessionData.chart?.scales?.x?.constructor?.name,
streamingEnabled: !!sessionData.chart?.$streaming?.enabled,
datasets: sessionData.chart?.data?.datasets?.length || 0,
dataPoints: sessionData.chart?.data?.datasets?.reduce((total, d) => total + (d.data?.length || 0), 0) || 0
};
console.log('Chart Config:', results.chartConfig);
// Verificar si la escala es realtime
if (results.chartConfig.scaleType !== 'realtime') {
results.errors.push('La escala X no es de tipo realtime');
console.error('❌ Escala X no es realtime:', results.chartConfig.scaleType);
} else {
console.log('✅ Escala realtime configurada correctamente');
}
// Verificar streaming
if (!results.chartConfig.streamingEnabled) {
results.errors.push('Streaming no está habilitado en el chart');
console.error('❌ Streaming no está habilitado');
} else {
console.log('✅ Streaming habilitado en el chart');
}
}
// 4. Verificar datos del backend
console.log('\n4⃣ VERIFICANDO DATOS DEL BACKEND...');
try {
const response = await fetch(`/api/plots/${sessionId}/data`);
results.backendData = await response.json();
console.log('📊 Respuesta del backend:', {
success: !!results.backendData.datasets,
datasets: results.backendData.datasets?.length || 0,
totalPoints: results.backendData.data_points_count || 0,
isActive: results.backendData.is_active,
isPaused: results.backendData.is_paused
});
if (results.backendData.datasets && results.backendData.datasets.length > 0) {
console.log('✅ Backend devuelve datos');
// Verificar estructura de datos
const firstDataset = results.backendData.datasets[0];
console.log('📈 Primer dataset:', {
label: firstDataset.label,
dataPoints: firstDataset.data?.length || 0,
samplePoint: firstDataset.data?.[0]
});
if (firstDataset.data && firstDataset.data.length > 0) {
console.log('✅ Dataset tiene puntos de datos');
// Verificar formato de timestamps
const firstPoint = firstDataset.data[0];
const currentTime = Date.now();
const timeDiff = Math.abs(currentTime - firstPoint.x);
console.log('⏰ Análisis de timestamps:', {
firstPointTime: firstPoint.x,
currentTime: currentTime,
differenceMs: timeDiff,
differenceSec: Math.round(timeDiff / 1000),
isRecent: timeDiff < 300000 // 5 minutos
});
if (timeDiff > 300000) {
results.errors.push('Los timestamps de los datos son muy antiguos');
console.warn('⚠️ Los datos parecen ser muy antiguos');
}
} else {
results.errors.push('El dataset no tiene puntos de datos');
console.error('❌ El dataset está vacío');
}
} else {
results.errors.push('Backend no devuelve datasets');
console.error('❌ Backend no devuelve datos válidos');
}
} catch (error) {
results.errors.push(`Error al obtener datos del backend: ${error.message}`);
console.error('❌ Error del backend:', error);
}
// 5. Test de funcionalidad streaming
console.log('\n5⃣ PROBANDO FUNCIONALIDAD STREAMING...');
if (sessionData && sessionData.chart && results.backendData) {
try {
// Intentar agregar un punto de prueba
const testResult = window.ChartStreaming.addStreamingData(sessionData.chart, 0, {
x: Date.now(),
y: Math.random() * 100
});
console.log('🧪 Test de addStreamingData:', testResult ? '✅' : '❌');
if (testResult) {
// Verificar si el punto se agregó
const currentDataPoints = sessionData.chart.data.datasets[0]?.data?.length || 0;
console.log('📊 Puntos después del test:', currentDataPoints);
results.streamingWorking = currentDataPoints > 0;
} else {
results.errors.push('addStreamingData falló');
}
} catch (error) {
results.errors.push(`Error en test de streaming: ${error.message}`);
console.error('❌ Error en test de streaming:', error);
}
}
// 6. Resumen final
console.log('\n6⃣ RESUMEN DEL DIAGNÓSTICO');
console.log('='.repeat(40));
if (results.errors.length === 0) {
console.log('🎉 No se encontraron errores graves');
if (!results.streamingWorking) {
console.log('⚠️ Sin embargo, el streaming no parece estar funcionando');
console.log('💡 Intenta: forceStreamingUpdate()');
}
} else {
console.log('❌ Errores encontrados:');
results.errors.forEach((error, index) => {
console.log(` ${index + 1}. ${error}`);
});
}
// 7. Recomendaciones
console.log('\n7⃣ PRÓXIMOS PASOS RECOMENDADOS:');
if (!results.librariesLoaded.chartStreaming) {
console.log('🔧 1. Recargar la página para asegurar que el plugin se carga');
}
if (results.errors.some(e => e.includes('timestamp'))) {
console.log('🔧 2. Ejecutar clearStreamingData() y reiniciar el plot');
}
if (results.backendData && !results.streamingWorking) {
console.log('🔧 3. Ejecutar enablePlotDebug() y forceStreamingUpdate()');
}
console.log('🔧 4. Si persiste: plotManager.controlPlot("' + sessionId + '", "stop") y luego "start"');
return results;
} catch (error) {
console.error('💥 Error durante el diagnóstico:', error);
results.errors.push(`Error fatal: ${error.message}`);
return results;
}
};
// Función para limpiar y reiniciar streaming
window.resetStreaming = function (sessionId = null) {
console.log('🔄 REINICIANDO STREAMING...');
const pm = window.plotManager || plotManager;
if (!pm || !pm.sessions) {
console.error('❌ PlotManager no disponible');
return false;
}
const targetSessionId = sessionId || Array.from(pm.sessions.keys())[0];
if (!targetSessionId) {
console.error('❌ No hay sesiones disponibles');
return false;
}
console.log(`🔄 Reiniciando sesión: ${targetSessionId}`);
try {
// 1. Limpiar datos existentes
if (pm.clearStreamingData) {
pm.clearStreamingData(targetSessionId);
console.log('✅ Datos de streaming limpiados');
}
// 2. Pausar y reiniciar
pm.controlPlot(targetSessionId, 'stop');
console.log('⏹️ Plot detenido');
setTimeout(() => {
pm.controlPlot(targetSessionId, 'start');
console.log('▶️ Plot reiniciado');
// 3. Forzar actualización después de un momento
setTimeout(() => {
if (pm.refreshStreamingData) {
const sessionData = pm.sessions.get(targetSessionId);
if (sessionData?.chart) {
pm.refreshStreamingData(targetSessionId, sessionData.chart);
console.log('🔄 Actualización forzada');
}
}
}, 1000);
}, 500);
return true;
} catch (error) {
console.error('❌ Error al reiniciar streaming:', error);
return false;
}
};
// Función de test rápido
window.quickStreamingTest = function () {
console.log('⚡ TEST RÁPIDO DE STREAMING');
console.log('-'.repeat(30));
const pm = window.plotManager || plotManager;
const checks = {
plotManager: !!pm,
sessions: pm?.sessions?.size || 0,
chartStreaming: !!window.ChartStreaming
};
Object.entries(checks).forEach(([key, value]) => {
console.log(`${key}: ${value ? '✅' : '❌'} ${value}`);
});
if (checks.sessions > 0) {
const sessionId = Array.from(pm.sessions.keys())[0];
console.log(`\n🧪 Probando sesión: ${sessionId}`);
// Ejecutar diagnóstico completo
return diagnoseStreamingIssue();
} else {
console.log('\n💡 Crea un plot primero, luego ejecuta: quickStreamingTest()');
return false;
}
};
// Función específica para diagnosticar el onRefresh callback
window.diagnoseOnRefreshIssue = function () {
console.log('🔧 DIAGNÓSTICO ESPECÍFICO DEL ONREFRESH CALLBACK');
console.log('='.repeat(50));
const pm = window.plotManager || plotManager;
if (!pm || !pm.sessions) {
console.error('❌ PlotManager no disponible');
console.log('💡 Tip: Espera unos segundos e intenta de nuevo');
return;
}
if (pm.sessions.size === 0) {
console.log('⚠️ No hay sesiones activas. Crea un plot primero.');
return;
}
const sessionId = Array.from(pm.sessions.keys())[0];
const sessionData = pm.sessions.get(sessionId);
if (!sessionData || !sessionData.chart) {
console.error('❌ No se encuentra el chart para la sesión');
return;
}
const chart = sessionData.chart;
const realTimeScale = chart.scales.x;
console.log('📊 Información del Chart:');
console.log('- Chart existe:', !!chart);
console.log('- Chart type:', chart.config.type);
console.log('- Chart scales:', Object.keys(chart.scales));
console.log('\n📈 Información de la Escala RealTime:');
console.log('- Scale type:', realTimeScale.type);
console.log('- Scale constructor:', realTimeScale.constructor.name);
console.log('- Scale options:', realTimeScale.options);
console.log('- Scale realtime options:', realTimeScale.options.realtime);
if (realTimeScale.realtime) {
console.log('\n⚙ Configuración RealTime:');
console.log('- Duration:', realTimeScale.realtime.duration);
console.log('- Refresh:', realTimeScale.realtime.refresh);
console.log('- Pause:', realTimeScale.realtime.pause);
console.log('- onRefresh type:', typeof realTimeScale.realtime.onRefresh);
console.log('- onRefresh value:', realTimeScale.realtime.onRefresh);
if (typeof realTimeScale.realtime.onRefresh === 'function') {
console.log('✅ onRefresh callback está configurado correctamente');
// Test manual del callback
console.log('\n🧪 Probando callback onRefresh manualmente...');
try {
realTimeScale.realtime.onRefresh(chart);
console.log('✅ Callback onRefresh ejecutado sin errores');
} catch (error) {
console.error('❌ Error al ejecutar callback onRefresh:', error);
}
} else {
console.error('❌ onRefresh callback NO está configurado como función');
}
} else {
console.error('❌ No se encontró configuración realtime en la escala');
}
// Verificar streaming en chart
if (chart.$streaming) {
console.log('\n🔄 Estado del Streaming:');
console.log('- Streaming enabled:', chart.$streaming.enabled);
console.log('- Interval ID:', chart.$streaming.intervalId);
console.log('- Interval activo:', !!chart.$streaming.intervalId);
} else {
console.error('❌ No se encontró objeto $streaming en el chart');
}
};
console.log('🔧 Scripts de diagnóstico cargados:');
console.log('- diagnoseStreamingIssue() - Diagnóstico completo');
console.log('- resetStreaming() - Reiniciar streaming');
console.log('- quickStreamingTest() - Test rápido');
console.log('- diagnoseOnRefreshIssue() - Diagnóstico específico del onRefresh');

View File

@ -4,20 +4,29 @@
// Refrescar log de eventos
function refreshEventLog() {
const limit = document.getElementById('log-limit').value;
const limitElement = document.getElementById('log-limit');
const limit = limitElement ? limitElement.value : 100;
fetch(`/api/events?limit=${limit}`)
.then(response => response.json())
.then(data => {
if (data.success) {
const logContainer = document.getElementById('events-log');
const logStats = document.getElementById('log-stats');
const logContainer = document.getElementById('events-container');
const logStats = document.getElementById('events-count');
// Verificar que los elementos existan
if (!logContainer) {
console.warn('Events container not found');
return;
}
// Limpiar entradas existentes
logContainer.innerHTML = '';
// Actualizar estadísticas
logStats.textContent = `Showing ${data.showing} of ${data.total_events} events`;
if (logStats) {
logStats.textContent = `${data.showing} of ${data.total_events}`;
}
// Añadir eventos (orden inverso para mostrar primero los más nuevos)
const events = data.events.reverse();
@ -103,7 +112,7 @@ function initEventListeners() {
}
// Función para cargar eventos en el tab de events
window.loadEvents = function() {
window.loadEvents = function () {
fetch('/api/events?limit=50')
.then(response => response.json())
.then(data => {
@ -111,11 +120,19 @@ window.loadEvents = function() {
const eventsContainer = document.getElementById('events-container');
const eventsCount = document.getElementById('events-count');
// Verificar que los elementos existan
if (!eventsContainer) {
console.warn('Events container not found in loadEvents');
return;
}
// Limpiar contenedor
eventsContainer.innerHTML = '';
// Actualizar contador
eventsCount.textContent = data.showing || 0;
if (eventsCount) {
eventsCount.textContent = data.showing || 0;
}
// Añadir eventos (orden inverso para mostrar primero los más nuevos)
const events = data.events.reverse();

View File

@ -175,13 +175,20 @@ class PlotManager {
async updateSessionData(sessionId) {
try {
// 🚀 NUEVO: Para streaming, el refreshStreamingData maneja la actualización automática
// Esta función ahora es principalmente para compatibilidad y casos especiales
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart) {
return;
}
const response = await fetch(`/api/plots/${sessionId}/data`);
const plotData = await response.json();
if (plotData.datasets) {
// 🔧 DEBUG: Log para troubleshooting
if (plotData.datasets.length > 1) {
plotDebugLog(`📈 Plot ${sessionId}: Updating ${plotData.datasets.length} variables, ${plotData.data_points_count} total points`);
plotDebugLog(`📈 Plot ${sessionId}: Manual update ${plotData.datasets.length} variables, ${plotData.data_points_count} total points`);
}
this.updateChart(sessionId, plotData);
} else {
@ -233,83 +240,57 @@ class PlotManager {
document.getElementById('plot-sessions-container').appendChild(container);
// Crear Chart.js
// 🚀 NUEVO: Usar chartjs-plugin-streaming para crear chart
const ctx = document.getElementById(`chart-${sessionId}`).getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0 // Sin animación para mejor performance
},
scales: {
x: {
type: 'time',
time: {
unit: 'second',
displayFormats: {
second: 'HH:mm:ss'
}
},
title: {
display: true,
text: 'Time'
}
},
y: {
title: {
display: true,
text: 'Value'
}
}
},
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
// Configurar streaming con la nueva librería
const streamingConfig = window.ChartStreaming.createStreamingChartConfig({
duration: (config.time_window || 60) * 1000, // Convertir a milisegundos
refresh: 500, // Actualizar cada 500ms
frameRate: 30,
pause: false,
yMin: config.y_min,
yMax: config.y_max,
onRefresh: (chart) => {
// Esta función se llama automáticamente para obtener nuevos datos
this.refreshStreamingData(sessionId, chart);
}
});
this.sessions.set(sessionId, chart);
const chart = new Chart(ctx, streamingConfig);
// Actualizar datos iniciales
this.updateSessionData(sessionId);
this.sessions.set(sessionId, {
chart: chart,
config: config,
lastDataFetch: 0,
datasets: new Map() // Para tracking de datasets por variable
});
console.log(`📈 Created plot session: ${sessionId}`);
// Inicializar datasets para las variables del plot
this.initializeStreamingDatasets(sessionId, config);
console.log(`📈 Created streaming plot session: ${sessionId}`);
}
updateChart(sessionId, plotData) {
const chart = this.sessions.get(sessionId);
if (!chart) {
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart) {
console.warn(`📈 Plot ${sessionId}: Chart not found in sessions`);
return;
}
const chart = sessionData.chart;
// 🔧 DEBUG: Verificar datos antes de actualizar
if (plotData.datasets && plotData.datasets.length > 0) {
plotDebugLog(`📈 Plot ${sessionId}: Updating chart with ${plotData.datasets.length} datasets`);
plotDebugLog(`📈 Plot ${sessionId}: Updating streaming chart with ${plotData.datasets.length} datasets`);
plotData.datasets.forEach((dataset, idx) => {
plotDebugLog(` - Variable ${idx + 1}: ${dataset.label} (${dataset.data.length} points)`);
});
}
// Actualizar datasets
chart.data.datasets = plotData.datasets || [];
// 🚀 NUEVO: Para streaming, agregamos nuevos datos en lugar de reemplazar todo
this.updateStreamingData(sessionId, plotData);
// Actualizar escalas Y - mejore el auto-scaling
if (plotData.y_min !== undefined && plotData.y_max !== undefined) {
@ -330,7 +311,7 @@ class PlotManager {
// 🔧 DEBUG: Log si el contador no coincide
const calculatedPoints = (plotData.datasets || []).reduce((sum, dataset) => sum + (dataset.data?.length || 0), 0);
if (totalPoints !== calculatedPoints) {
console.warn(`📈 Plot ${sessionId}: Points mismatch - reported: ${totalPoints}, calculated: ${calculatedPoints}`);
plotDebugLog(`📈 Plot ${sessionId}: Points mismatch - reported: ${totalPoints}, calculated: ${calculatedPoints}`);
}
}
@ -346,12 +327,20 @@ class PlotManager {
});
}
// Actualizar chart
chart.update('none');
// El chart de streaming se actualiza automáticamente, no necesitamos llamar update manualmente
}
async controlPlot(sessionId, action) {
try {
// 🚀 NUEVO: Controlar streaming localmente para algunas acciones
if (action === 'pause') {
this.setStreamingPause(sessionId, true);
} else if (action === 'start') {
this.setStreamingPause(sessionId, false);
} else if (action === 'clear') {
this.clearStreamingData(sessionId);
}
const response = await fetch(`/api/plots/${sessionId}/control`, {
method: 'POST',
headers: {
@ -366,8 +355,15 @@ class PlotManager {
// 🔑 NUEVO: Actualizar status inmediatamente después de la acción
await this.updateSessionStatus(sessionId);
// Actualizar datos inmediatamente
await this.updateSessionData(sessionId);
// Para stop, también pausar el streaming
if (action === 'stop') {
this.setStreamingPause(sessionId, true);
}
// Para start, asegurar que el streaming esté activo
if (action === 'start') {
this.setStreamingPause(sessionId, false);
}
showNotification(result.message, 'success');
} else {
@ -387,6 +383,17 @@ class PlotManager {
if (data.success && data.config) {
this.updatePlotStats(sessionId, data.config);
// 🚀 NUEVO: Actualizar estado de streaming basado en el status del backend
const sessionData = this.sessions.get(sessionId);
if (sessionData) {
// Actualizar configuración local
sessionData.config = data.config;
// Controlar pausa del streaming basado en el estado del plot
const shouldPause = !data.config.is_active || data.config.is_paused;
this.setStreamingPause(sessionId, shouldPause);
}
}
} catch (error) {
console.error(`Error updating session status ${sessionId}:`, error);
@ -415,9 +422,9 @@ class PlotManager {
removeChart(sessionId) {
// Destruir Chart.js
const chart = this.sessions.get(sessionId);
if (chart) {
chart.destroy();
const sessionData = this.sessions.get(sessionId);
if (sessionData && sessionData.chart) {
sessionData.chart.destroy();
this.sessions.delete(sessionId);
}
@ -428,70 +435,266 @@ class PlotManager {
}
}
// 🚀 NUEVAS FUNCIONES PARA STREAMING
/**
* Inicializa los datasets de streaming para las variables del plot
*/
initializeStreamingDatasets(sessionId, config) {
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart || !config.variables) {
plotDebugLog(`📈 Plot ${sessionId}: Cannot initialize datasets - missing data`);
return;
}
const chart = sessionData.chart;
const datasets = [];
plotDebugLog(`📈 Plot ${sessionId}: Initializing ${config.variables.length} streaming datasets`);
config.variables.forEach((variable, index) => {
const color = this.getColor(variable, index);
const dataset = {
label: variable,
data: [],
borderColor: color,
backgroundColor: color + '20', // Color con transparencia
borderWidth: 2,
fill: false,
pointRadius: 0,
pointHoverRadius: 3,
tension: 0.1
};
datasets.push(dataset);
sessionData.datasets.set(variable, index);
plotDebugLog(`📈 Plot ${sessionId}: Dataset ${index}: ${variable} (color: ${color})`);
});
// Inicializar timestamps tracking
sessionData.lastTimestamps = {};
chart.data.datasets = datasets;
chart.update('quiet');
plotDebugLog(`📈 Plot ${sessionId}: Successfully initialized ${datasets.length} streaming datasets`);
// 🔧 DEBUG: Verificar configuración del chart
plotDebugLog(`📈 Plot ${sessionId}: Chart config:`, {
type: chart.config.type,
scaleType: chart.scales.x?.type,
hasRealTimeScale: chart.scales.x?.constructor?.name,
streamingEnabled: !!chart.$streaming?.enabled
});
}
/**
* Función llamada automáticamente por chartjs-plugin-streaming para obtener nuevos datos
*/
async refreshStreamingData(sessionId, chart) {
try {
// Evitar llamadas muy frecuentes
const sessionData = this.sessions.get(sessionId);
if (!sessionData) return;
const now = Date.now();
if (now - sessionData.lastDataFetch < 800) { // Mínimo 800ms entre llamadas
return;
}
sessionData.lastDataFetch = now;
// 🔧 DEBUG: Log de llamada
plotDebugLog(`📈 Plot ${sessionId}: Fetching data from backend...`);
// Obtener datos del backend
const response = await fetch(`/api/plots/${sessionId}/data`);
const plotData = await response.json();
plotDebugLog(`📈 Plot ${sessionId}: Received data:`, plotData);
if (plotData && plotData.datasets && plotData.datasets.length > 0) {
// Agregar los nuevos puntos de datos al chart de streaming
this.processBackendDataForStreaming(sessionId, plotData);
// Actualizar contador de puntos
const pointsElement = document.getElementById(`points-${sessionId}`);
if (pointsElement) {
pointsElement.textContent = plotData.data_points_count || 0;
}
} else {
plotDebugLog(`📈 Plot ${sessionId}: No datasets received or empty data`);
}
} catch (error) {
console.error(`Error refreshing streaming data for ${sessionId}:`, error);
}
}
/**
* Actualiza los datos de streaming del chart
*/
updateStreamingData(sessionId, plotData) {
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart || !plotData.datasets) {
return;
}
const chart = sessionData.chart;
// Para cada dataset del backend, agregar los nuevos puntos
plotData.datasets.forEach((backendDataset, index) => {
if (chart.data.datasets[index] && backendDataset.data) {
const chartDataset = chart.data.datasets[index];
// Obtener los últimos puntos que no tengamos aún
const existingPoints = chartDataset.data.length;
const newPoints = backendDataset.data.slice(existingPoints);
newPoints.forEach(point => {
window.ChartStreaming.addStreamingData(chart, index, {
x: point.x,
y: point.y
});
});
plotDebugLog(`📈 Plot ${sessionId}: Added ${newPoints.length} new points to dataset ${index}`);
}
});
}
/**
* Procesa datos del backend para el chart de streaming
*/
processBackendDataForStreaming(sessionId, plotData) {
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart || !plotData.datasets) {
return;
}
const chart = sessionData.chart;
// Asegurar que tenemos tracking de los últimos timestamps por dataset
if (!sessionData.lastTimestamps) {
sessionData.lastTimestamps = {};
}
plotDebugLog(`📈 Plot ${sessionId}: Processing ${plotData.datasets.length} datasets for streaming`);
// Para cada dataset del backend
plotData.datasets.forEach((backendDataset, index) => {
const variableName = backendDataset.label;
if (!backendDataset.data || backendDataset.data.length === 0) {
plotDebugLog(`📈 Plot ${sessionId}: Dataset ${variableName} has no data`);
return;
}
// Obtener el último timestamp que agregamos para esta variable
const lastTimestamp = sessionData.lastTimestamps[variableName] || 0;
// Filtrar solo los puntos nuevos (timestamp mayor al último que agregamos)
const newPoints = backendDataset.data.filter(point => point.x > lastTimestamp);
if (newPoints.length > 0) {
plotDebugLog(`📈 Plot ${sessionId}: Adding ${newPoints.length} new points for ${variableName}`);
// Agregar cada nuevo punto al chart de streaming
newPoints.forEach(point => {
if (point.y !== null && point.y !== undefined) {
window.ChartStreaming.addStreamingData(chart, index, {
x: point.x, // Usar timestamp del backend
y: point.y
});
}
});
// Actualizar el último timestamp procesado para esta variable
const latestPoint = newPoints[newPoints.length - 1];
sessionData.lastTimestamps[variableName] = latestPoint.x;
plotDebugLog(`📈 Plot ${sessionId}: Updated last timestamp for ${variableName} to ${latestPoint.x}`);
} else {
plotDebugLog(`📈 Plot ${sessionId}: No new points for ${variableName} (last: ${lastTimestamp})`);
}
});
}
/**
* Controla la pausa/reanudación del streaming
*/
setStreamingPause(sessionId, paused) {
const sessionData = this.sessions.get(sessionId);
if (sessionData && sessionData.chart) {
window.ChartStreaming.setStreamingPause(sessionData.chart, paused);
plotDebugLog(`📈 Plot ${sessionId}: Streaming ${paused ? 'paused' : 'resumed'}`);
}
}
/**
* Limpia todos los datos de streaming
*/
clearStreamingData(sessionId) {
const sessionData = this.sessions.get(sessionId);
if (sessionData && sessionData.chart) {
window.ChartStreaming.clearStreamingData(sessionData.chart);
plotDebugLog(`📈 Plot ${sessionId}: Streaming data cleared`);
}
}
async createPlotSessionTab(sessionId, sessionInfo) {
// Crear tab dinámico para el plot
if (typeof tabManager !== 'undefined') {
tabManager.createPlotTab(sessionId, sessionInfo.name || `Plot ${sessionId}`);
}
// Crear el Chart.js en el canvas del tab
// 🚀 NUEVO: Obtener configuración completa del plot para el streaming
let plotConfig = sessionInfo;
try {
const configResponse = await fetch(`/api/plots/${sessionId}/config`);
const configData = await configResponse.json();
if (configData.success && configData.config) {
plotConfig = configData.config;
}
} catch (error) {
console.warn(`Could not load plot config for ${sessionId}, using basic info`);
}
// Crear el Chart.js con streaming en el canvas del tab
const ctx = document.getElementById(`chart-${sessionId}`).getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0 // Sin animación para mejor performance
},
scales: {
x: {
type: 'time',
time: {
unit: 'second',
displayFormats: {
second: 'HH:mm:ss'
}
},
title: {
display: true,
text: 'Time'
}
},
y: {
title: {
display: true,
text: 'Value'
}
}
},
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
// Configurar streaming con la nueva librería
const streamingConfig = window.ChartStreaming.createStreamingChartConfig({
duration: (plotConfig.time_window || 60) * 1000, // Convertir a milisegundos
refresh: 500, // Actualizar cada 500ms
frameRate: 30,
pause: !plotConfig.is_active, // Pausar si no está activo
yMin: plotConfig.y_min,
yMax: plotConfig.y_max,
onRefresh: (chart) => {
// Esta función se llama automáticamente para obtener nuevos datos
this.refreshStreamingData(sessionId, chart);
}
});
this.sessions.set(sessionId, chart);
const chart = new Chart(ctx, streamingConfig);
this.sessions.set(sessionId, {
chart: chart,
config: plotConfig,
lastDataFetch: 0,
datasets: new Map() // Para tracking de datasets por variable
});
// Inicializar datasets para las variables del plot
if (plotConfig.variables) {
this.initializeStreamingDatasets(sessionId, plotConfig);
}
// Actualizar estadísticas del plot
this.updatePlotStats(sessionId, sessionInfo);
console.log(`📈 Created plot tab and chart for session: ${sessionId}`);
console.log(`📈 Created streaming plot tab and chart for session: ${sessionId}`);
}
updatePlotStats(sessionId, sessionInfo) {
@ -1205,8 +1408,10 @@ class PlotManager {
this.stopAutoUpdate();
// Destruir todos los charts
for (const [sessionId, chart] of this.sessions) {
chart.destroy();
for (const [sessionId, sessionData] of this.sessions) {
if (sessionData && sessionData.chart) {
sessionData.chart.destroy();
}
}
this.sessions.clear();
@ -1214,6 +1419,81 @@ class PlotManager {
}
}
// 🔧 NUEVAS FUNCIONES DE DEBUG Y VERIFICACIÓN
/**
* Verifica que chartjs-plugin-streaming esté cargado correctamente
*/
window.verifyStreamingIntegration = function () {
console.log('🧪 Verificando integración de Chart.js Streaming...');
const checks = {
chartjsLoaded: typeof Chart !== 'undefined',
streamingLoaded: typeof window.ChartStreaming !== 'undefined',
plotManagerLoaded: typeof plotManager !== 'undefined',
activeStreamingSessions: 0
};
console.log('✅ Chart.js cargado:', checks.chartjsLoaded);
console.log('✅ ChartStreaming cargado:', checks.streamingLoaded);
console.log('✅ PlotManager cargado:', checks.plotManagerLoaded);
if (checks.plotManagerLoaded && plotManager.sessions) {
checks.activeStreamingSessions = plotManager.sessions.size;
console.log('✅ Sesiones de streaming activas:', checks.activeStreamingSessions);
// Verificar cada sesión
for (const [sessionId, sessionData] of plotManager.sessions) {
console.log(`📈 Sesión ${sessionId}:`, {
hasChart: !!sessionData.chart,
chartType: sessionData.chart?.config?.type,
scaleType: sessionData.chart?.scales?.x?.type,
datasets: sessionData.chart?.data?.datasets?.length || 0,
lastTimestamps: sessionData.lastTimestamps,
streaming: !!sessionData.chart?.$streaming?.enabled
});
}
}
if (checks.streamingLoaded) {
console.log('✅ ChartStreaming functions:', Object.keys(window.ChartStreaming));
// Test de configuración
try {
const testConfig = window.ChartStreaming.createStreamingChartConfig({
duration: 10000,
refresh: 1000
});
console.log('✅ Test configuración streaming:', !!testConfig);
} catch (e) {
console.log('❌ Error en test configuración:', e.message);
}
}
return checks;
};
/**
* Fuerza una actualización de datos para todas las sesiones
*/
window.forceStreamingUpdate = function () {
if (!plotManager || !plotManager.sessions) {
console.log('❌ PlotManager no disponible');
return;
}
console.log('🔄 Forzando actualización de streaming...');
for (const [sessionId, sessionData] of plotManager.sessions) {
console.log(`🔄 Actualizando sesión ${sessionId}...`);
if (sessionData.chart) {
// Forzar refresh de datos
plotManager.refreshStreamingData(sessionId, sessionData.chart);
}
}
};
// Función global para toggle de trigger config
window.togglePlotFormTriggerConfig = function () {
const triggerEnabled = document.getElementById('plot-form-trigger-enabled');
@ -1298,8 +1578,38 @@ window.showNotification = function (message, type = 'info') {
let plotManager = null;
document.addEventListener('DOMContentLoaded', function () {
console.log('📈 Initializing Plot Manager with streaming support...');
// Verificar que chartjs-plugin-streaming esté cargado
if (typeof window.ChartStreaming === 'undefined') {
console.error('❌ ChartStreaming plugin not loaded! The streaming functionality will not work.');
console.log('💡 Make sure chartjs-plugin-streaming.js is loaded before plotting.js');
} else {
console.log('✅ ChartStreaming plugin loaded successfully');
console.log('Available functions:', Object.keys(window.ChartStreaming));
}
// Exportar clase PlotManager globalmente
window.PlotManager = PlotManager;
// Inicializar Plot Manager
plotManager = new PlotManager();
window.plotManager = new PlotManager();
// Para compatibilidad
plotManager = window.plotManager;
// Auto-verificar integración después de un breve delay
setTimeout(() => {
window.verifyStreamingIntegration();
// Si hay plots activos, mostrar información de debugging
if (plotManager.sessions.size > 0) {
console.log('📈 Active plot sessions detected. To debug:');
console.log('- enablePlotDebug() - Enable detailed logging');
console.log('- forceStreamingUpdate() - Force data update');
console.log('- verifyStreamingIntegration() - Check system status');
}
}, 2000);
// Cerrar modales con Escape (pero prevenir cierre accidental)
document.addEventListener('keydown', function (e) {

133
static/js/quick-fix-test.js Normal file
View File

@ -0,0 +1,133 @@
/**
* 🧪 Test Rápido de Verificación Post-Fix
* Ejecutar después de cargar la página para verificar que todo funciona
*/
window.quickFixTest = function () {
console.log('🧪 TEST RÁPIDO POST-FIX');
console.log('='.repeat(40));
const results = {
duplicatedElements: false,
plotManagerAvailable: false,
eventsWorking: false,
chartStreamingLoaded: false,
debugFunctionsLoaded: false
};
// 1. Verificar elementos duplicados
console.log('\n1⃣ VERIFICANDO ELEMENTOS DUPLICADOS...');
const eventsContainers = document.querySelectorAll('#events-container');
const refreshBtns = document.querySelectorAll('#refresh-events-btn');
console.log(`- events-container: ${eventsContainers.length} (esperado: 1)`);
console.log(`- refresh-events-btn: ${refreshBtns.length} (esperado: 1)`);
results.duplicatedElements = eventsContainers.length === 1 && refreshBtns.length === 1;
console.log(results.duplicatedElements ? '✅ Sin duplicados' : '❌ Hay elementos duplicados');
// 2. Verificar PlotManager
console.log('\n2⃣ VERIFICANDO PLOTMANAGER...');
const pm = window.plotManager || plotManager;
results.plotManagerAvailable = !!pm;
console.log(`- window.plotManager: ${!!window.plotManager}`);
console.log(`- plotManager global: ${!!plotManager}`);
console.log(`- PlotManager class: ${!!window.PlotManager}`);
console.log(results.plotManagerAvailable ? '✅ PlotManager disponible' : '❌ PlotManager no disponible');
// 3. Verificar Events
console.log('\n3⃣ VERIFICANDO EVENTS...');
const eventsContainer = document.getElementById('events-container');
const eventsCount = document.getElementById('events-count');
console.log(`- events-container existe: ${!!eventsContainer}`);
console.log(`- events-count existe: ${!!eventsCount}`);
results.eventsWorking = !!eventsContainer && !!eventsCount;
console.log(results.eventsWorking ? '✅ Elements de events OK' : '❌ Faltan elementos de events');
// 4. Verificar Chart.js Streaming
console.log('\n4⃣ VERIFICANDO CHART.JS STREAMING...');
results.chartStreamingLoaded = !!window.ChartStreaming;
if (results.chartStreamingLoaded) {
const functions = Object.keys(window.ChartStreaming);
console.log(`✅ ChartStreaming cargado con ${functions.length} funciones`);
console.log(`- Funciones: ${functions.join(', ')}`);
} else {
console.log('❌ ChartStreaming no cargado');
}
// 5. Verificar funciones de debug
console.log('\n5⃣ VERIFICANDO FUNCIONES DE DEBUG...');
const debugFunctions = [
'diagnoseStreamingIssue',
'resetStreaming',
'quickStreamingTest',
'diagnoseOnRefreshIssue'
];
const availableFunctions = debugFunctions.filter(fn => typeof window[fn] === 'function');
results.debugFunctionsLoaded = availableFunctions.length === debugFunctions.length;
console.log(`✅ Funciones cargadas: ${availableFunctions.join(', ')}`);
if (availableFunctions.length < debugFunctions.length) {
const missing = debugFunctions.filter(fn => typeof window[fn] !== 'function');
console.log(`❌ Funciones faltantes: ${missing.join(', ')}`);
}
// 6. Resumen
console.log('\n6⃣ RESUMEN FINAL');
console.log('='.repeat(30));
const allGood = Object.values(results).every(Boolean);
if (allGood) {
console.log('🎉 ¡TODOS LOS PROBLEMAS ARREGLADOS!');
console.log('✅ Ya puedes crear plots y usar el diagnóstico');
console.log('');
console.log('📋 Próximos pasos:');
console.log('1. Ve a la pestaña "Real-Time Plotting"');
console.log('2. Crea un nuevo plot');
console.log('3. Ejecuta diagnoseOnRefreshIssue() para verificar streaming');
} else {
console.log('⚠️ Algunos problemas persisten:');
Object.entries(results).forEach(([key, value]) => {
if (!value) {
console.log(`${key}`);
}
});
console.log('');
console.log('💡 Intenta recargar la página (F5) y ejecutar quickFixTest() de nuevo');
}
return results;
};
// Test básico de events
window.testEventsIntegration = function () {
console.log('🧪 PROBANDO INTEGRACIÓN DE EVENTS...');
try {
refreshEventLog();
console.log('✅ refreshEventLog() ejecutado sin errores');
// Test loadEvents
if (typeof window.loadEvents === 'function') {
window.loadEvents();
console.log('✅ loadEvents() ejecutado sin errores');
} else {
console.log('❌ loadEvents() no disponible');
}
return true;
} catch (error) {
console.error('❌ Error en events:', error);
return false;
}
};
console.log('🧪 Funciones de test cargadas:');
console.log('- quickFixTest() - Verificación completa post-fix');
console.log('- testEventsIntegration() - Test específico de events');

216
static/js/test-streaming.js Normal file
View File

@ -0,0 +1,216 @@
/**
* 🧪 Script de prueba para Chart.js Plugin Streaming
* Ejecutar en consola del navegador para verificar integración
*/
// Test básico de disponibilidad
function testStreamingAvailability() {
console.log('🧪 Testing Chart.js Streaming Plugin availability...');
let results = {
chartjsLoaded: typeof Chart !== 'undefined',
streamingLoaded: typeof window.ChartStreaming !== 'undefined',
realTimeScaleRegistered: false,
streamingPluginRegistered: false
};
// Verificar que Chart.js esté cargado
if (results.chartjsLoaded) {
console.log('✅ Chart.js is loaded');
// Verificar escala realtime
try {
Chart.register(window.ChartStreaming.RealTimeScale);
results.realTimeScaleRegistered = true;
console.log('✅ RealTime scale is available');
} catch (e) {
console.log('❌ RealTime scale registration failed:', e.message);
}
// Verificar plugin de streaming
try {
Chart.register(window.ChartStreaming.streamingPlugin);
results.streamingPluginRegistered = true;
console.log('✅ Streaming plugin is available');
} catch (e) {
console.log('❌ Streaming plugin registration failed:', e.message);
}
} else {
console.log('❌ Chart.js is not loaded');
}
// Verificar ChartStreaming global
if (results.streamingLoaded) {
console.log('✅ ChartStreaming global object is available');
console.log('Available functions:', Object.keys(window.ChartStreaming));
} else {
console.log('❌ ChartStreaming global object is not available');
}
return results;
}
// Test de creación de configuración
function testStreamingConfig() {
console.log('🧪 Testing streaming configuration creation...');
if (typeof window.ChartStreaming === 'undefined') {
console.log('❌ ChartStreaming not available');
return false;
}
try {
const config = window.ChartStreaming.createStreamingChartConfig({
duration: 30000,
refresh: 1000,
yMin: 0,
yMax: 100
});
console.log('✅ Streaming configuration created successfully');
console.log('Config structure:', {
type: config.type,
hasRealTimeScale: config.options.scales.x.type === 'realtime',
duration: config.options.scales.x.realtime.duration,
refresh: config.options.scales.x.realtime.refresh
});
return true;
} catch (e) {
console.log('❌ Failed to create streaming configuration:', e.message);
return false;
}
}
// Test de creación de chart (requiere canvas)
function testStreamingChart() {
console.log('🧪 Testing streaming chart creation...');
// Buscar un canvas existente o crear uno temporal
let canvas = document.querySelector('canvas');
let isTemporary = false;
if (!canvas) {
console.log('Creating temporary canvas for testing...');
canvas = document.createElement('canvas');
canvas.id = 'test-streaming-canvas';
canvas.style.display = 'none';
document.body.appendChild(canvas);
isTemporary = true;
}
try {
const ctx = canvas.getContext('2d');
const config = window.ChartStreaming.createStreamingChartConfig({
duration: 10000,
refresh: 2000
});
const chart = new Chart(ctx, config);
console.log('✅ Streaming chart created successfully');
console.log('Chart info:', {
id: chart.id,
type: chart.config.type,
hasStreamingPlugin: chart.config.plugins.includes('streaming'),
scaleType: chart.scales.x.type
});
// Cleanup
chart.destroy();
if (isTemporary) {
canvas.remove();
}
return true;
} catch (e) {
console.log('❌ Failed to create streaming chart:', e.message);
// Cleanup en caso de error
if (isTemporary && canvas.parentNode) {
canvas.remove();
}
return false;
}
}
// Test completo
function runStreamingTests() {
console.log('🚀 Starting Chart.js Streaming Plugin Integration Tests');
console.log('='.repeat(60));
const results = {
availability: testStreamingAvailability(),
config: testStreamingConfig(),
chart: testStreamingChart()
};
console.log('='.repeat(60));
console.log('📊 Test Results Summary:');
console.log('- Availability:', results.availability.chartjsLoaded && results.availability.streamingLoaded ? '✅' : '❌');
console.log('- Configuration:', results.config ? '✅' : '❌');
console.log('- Chart Creation:', results.chart ? '✅' : '❌');
const allPassed = results.availability.chartjsLoaded &&
results.availability.streamingLoaded &&
results.config &&
results.chart;
if (allPassed) {
console.log('🎉 All tests passed! Chart.js Streaming Plugin is ready to use.');
} else {
console.log('⚠️ Some tests failed. Check the logs above for details.');
}
return results;
}
// Test específico para la aplicación PLC
function testPlotManagerIntegration() {
console.log('🧪 Testing PlotManager integration...');
if (typeof plotManager === 'undefined') {
console.log('❌ PlotManager not available (might not be on plotting tab)');
return false;
}
console.log('✅ PlotManager is available');
console.log('PlotManager info:', {
isInitialized: plotManager.isInitialized,
sessionsCount: plotManager.sessions.size,
colorsAvailable: plotManager.colors.length
});
// Verificar métodos de streaming
const streamingMethods = [
'setStreamingPause',
'clearStreamingData',
'refreshStreamingData',
'initializeStreamingDatasets'
];
const availableMethods = streamingMethods.filter(method =>
typeof plotManager[method] === 'function'
);
console.log('Available streaming methods:', availableMethods);
return availableMethods.length === streamingMethods.length;
}
// Hacer funciones disponibles globalmente para testing manual
if (typeof window !== 'undefined') {
window.testStreamingAvailability = testStreamingAvailability;
window.testStreamingConfig = testStreamingConfig;
window.testStreamingChart = testStreamingChart;
window.runStreamingTests = runStreamingTests;
window.testPlotManagerIntegration = testPlotManagerIntegration;
console.log('🧪 Streaming test functions loaded. Available commands:');
console.log('- testStreamingAvailability()');
console.log('- testStreamingConfig()');
console.log('- testStreamingChart()');
console.log('- runStreamingTests()');
console.log('- testPlotManagerIntegration()');
}

View File

@ -7,5 +7,5 @@
]
},
"auto_recovery_enabled": true,
"last_update": "2025-07-21T14:43:41.538809"
"last_update": "2025-07-21T18:34:08.746917"
}

View File

@ -669,32 +669,6 @@
</div>
</div>
<!-- 📋 EVENTS TAB -->
<div class="tab-content" id="events-tab">
<article>
<header>📋 Events & System Logs</header>
<div class="info-section">
<p><strong>📋 Event Logging:</strong> Monitor system events, errors, and operational status</p>
<p><strong>🔍 Real-time Updates:</strong> Events are automatically updated as they occur</p>
<p><strong>📊 Filtering:</strong> Filter events by type and time range</p>
</div>
<div class="log-controls">
<button id="refresh-events-btn">🔄 Refresh Events</button>
<button id="clear-events-btn" class="secondary">🗑️ Clear Events</button>
<div class="log-stats">
<span id="events-count">Loading...</span> events
</div>
</div>
<div class="log-container" id="events-container">
<p>Loading events...</p>
</div>
</article>
</div>
<!-- Variable Selection Modal (NEW) -->
<div id="variable-selection-modal" class="modal" style="display: none;">
<div class="modal-content variable-modal">
@ -784,6 +758,9 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
<!-- Chart.js Streaming Plugin -->
<script src="/static/js/chartjs-streaming/chartjs-plugin-streaming.js"></script>
<!-- JavaScript Modules -->
<script src="/static/js/utils.js"></script>
<script src="/static/js/theme.js"></script>
@ -797,6 +774,12 @@
<script src="/static/js/tabs.js"></script>
<script src="/static/js/plotting.js"></script>
<script src="/static/js/main.js"></script>
<!-- Debug Streaming Script - Debe cargarse al final -->
<script src="/static/js/debug-streaming.js"></script>
<!-- Quick Fix Test Script -->
<script src="/static/js/quick-fix-test.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,378 @@
/**
* 📈 Chart.js Plugin Streaming Integration
* Integra chartjs-plugin-streaming para plotting en tiempo real
*
* Combinación de módulos:
* - helpers.streaming.js
* - scale.realtime.js
* - plugin.streaming.js
* - plugin.zoom.js (integración con zoom)
*/
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
factory(exports, require('chart.js'));
} else if (typeof define === 'function' && define.amd) {
define(['exports', 'chart.js'], factory);
} else {
global = global || self;
factory(global.ChartStreaming = {}, global.Chart);
}
})(this, function (exports, Chart) {
'use strict';
// ============= HELPERS.STREAMING.JS =============
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
function resolveOption(scale, key) {
const realtimeOptions = scale.options.realtime || {};
const scaleOptions = scale.options;
if (realtimeOptions[key] !== undefined) {
return realtimeOptions[key];
}
if (scaleOptions[key] !== undefined) {
return scaleOptions[key];
}
// Valores por defecto
const defaults = {
duration: 10000,
delay: 0,
refresh: 1000,
frameRate: 30,
pause: false,
ttl: undefined,
onRefresh: null
};
return defaults[key];
}
function getAxisMap(element, keys, meta) {
const axis = meta.vAxisID || 'y';
return keys[axis] || [];
}
// ============= SCALE.REALTIME.JS (Simplificado) =============
class RealTimeScale extends Chart.Scale {
constructor(cfg) {
super(cfg);
this.type = 'realtime';
}
init(scaleOptions, scaleContext) {
super.init(scaleOptions, scaleContext);
const me = this;
const chart = me.chart;
const streaming = chart.$streaming = chart.$streaming || {};
// Inicializar opciones de tiempo real
const realtimeOptions = me.options.realtime || {};
me.realtime = {
duration: resolveOption(me, 'duration'),
delay: resolveOption(me, 'delay'),
refresh: resolveOption(me, 'refresh'),
frameRate: resolveOption(me, 'frameRate'),
pause: resolveOption(me, 'pause'),
ttl: resolveOption(me, 'ttl'),
onRefresh: resolveOption(me, 'onRefresh')
};
// Configurar intervalo de actualización
if (!streaming.intervalId && me.realtime.refresh > 0) {
streaming.intervalId = setInterval(() => {
if (!me.realtime.pause && typeof me.realtime.onRefresh === 'function') {
me.realtime.onRefresh(chart);
}
me.update();
chart.update('quiet');
}, me.realtime.refresh);
}
}
update(args) {
const me = this;
const chart = me.chart;
if (!chart.data || !chart.data.datasets) {
return;
}
const now = Date.now();
const duration = me.realtime.duration;
const delay = me.realtime.delay;
const ttl = me.realtime.ttl;
// Calcular ventana de tiempo
me.max = now - delay;
me.min = me.max - duration;
// Limpiar datos antiguos si TTL está configurado
if (ttl) {
const cutoff = now - ttl;
chart.data.datasets.forEach(dataset => {
if (dataset.data) {
dataset.data = dataset.data.filter(point => {
return point.x > cutoff;
});
}
});
}
super.update(args);
}
destroy() {
const me = this;
const chart = me.chart;
const streaming = chart.$streaming;
if (streaming && streaming.intervalId) {
clearInterval(streaming.intervalId);
delete streaming.intervalId;
}
super.destroy();
}
static id = 'realtime';
static defaults = {
realtime: {
duration: 10000,
delay: 0,
refresh: 1000,
frameRate: 30,
pause: false,
ttl: undefined,
onRefresh: null
},
time: {
unit: 'second',
displayFormats: {
second: 'HH:mm:ss'
}
}
};
}
// ============= PLUGIN.STREAMING.JS (Simplificado) =============
const streamingPlugin = {
id: 'streaming',
beforeInit(chart) {
const streaming = chart.$streaming = chart.$streaming || {};
streaming.enabled = false;
// Detectar si hay escalas realtime
const scales = chart.options.scales || {};
Object.keys(scales).forEach(scaleId => {
if (scales[scaleId].type === 'realtime') {
streaming.enabled = true;
}
});
},
afterInit(chart) {
const streaming = chart.$streaming;
if (streaming && streaming.enabled) {
// Configurar actualización automática
const update = chart.update;
chart.update = function (mode) {
if (mode === 'quiet') {
// Actualización silenciosa para streaming
Chart.prototype.update.call(this, mode);
} else {
update.call(this, mode);
}
};
}
},
beforeUpdate(chart) {
const streaming = chart.$streaming;
if (!streaming || !streaming.enabled) return;
// Permitir que las líneas Bézier se extiendan fuera del área del gráfico
const elements = chart.options.elements || {};
if (elements.line) {
elements.line.capBezierPoints = false;
}
},
destroy(chart) {
const streaming = chart.$streaming;
if (streaming && streaming.intervalId) {
clearInterval(streaming.intervalId);
delete streaming.intervalId;
}
delete chart.$streaming;
}
};
// ============= REGISTRO DE COMPONENTES =============
// Registrar escala realtime
Chart.register(RealTimeScale);
// Registrar plugin de streaming
Chart.register(streamingPlugin);
// ============= UTILIDADES PARA LA APLICACIÓN =============
/**
* Crea una configuración de Chart.js optimizada para streaming
*/
function createStreamingChartConfig(options = {}) {
const config = {
type: 'line',
data: {
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false, // Desactivar animaciones para mejor performance
scales: {
x: {
type: 'realtime',
realtime: {
duration: options.duration || 60000, // 60 segundos por defecto
delay: options.delay || 0,
refresh: options.refresh || 1000, // 1 segundo
frameRate: options.frameRate || 30,
pause: options.pause || false,
ttl: options.ttl || undefined,
onRefresh: options.onRefresh || null
},
title: {
display: true,
text: 'Tiempo'
}
},
y: {
title: {
display: true,
text: 'Valor'
},
min: options.yMin,
max: options.yMax
}
},
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
},
elements: {
point: {
radius: 0, // Sin puntos para mejor performance
hoverRadius: 3
},
line: {
tension: 0.1,
borderWidth: 2
}
}
},
plugins: ['streaming']
};
return config;
}
/**
* Agrega datos a un dataset de streaming
*/
function addStreamingData(chart, datasetIndex, data) {
if (!chart || !chart.data || !chart.data.datasets[datasetIndex]) {
return;
}
const dataset = chart.data.datasets[datasetIndex];
if (!dataset.data) {
dataset.data = [];
}
// Agregar nuevo punto con timestamp
const timestamp = data.x || Date.now();
dataset.data.push({
x: timestamp,
y: data.y
});
// Chart.js se encarga automáticamente de eliminar datos antiguos
// basado en la configuración de TTL y duration de la escala realtime
}
/**
* Controla la pausa/reanudación del streaming
*/
function setStreamingPause(chart, paused) {
if (!chart || !chart.$streaming) return;
const scales = chart.scales;
Object.keys(scales).forEach(scaleId => {
const scale = scales[scaleId];
if (scale instanceof RealTimeScale) {
scale.realtime.pause = paused;
}
});
}
/**
* Limpia todos los datos de streaming
*/
function clearStreamingData(chart) {
if (!chart || !chart.data) return;
chart.data.datasets.forEach(dataset => {
if (dataset.data) {
dataset.data.length = 0;
}
});
chart.update('quiet');
}
// ============= EXPORTS =============
// Exportar para uso en la aplicación
exports.RealTimeScale = RealTimeScale;
exports.streamingPlugin = streamingPlugin;
exports.createStreamingChartConfig = createStreamingChartConfig;
exports.addStreamingData = addStreamingData;
exports.setStreamingPause = setStreamingPause;
exports.clearStreamingData = clearStreamingData;
// Hacer disponible globalmente
if (typeof window !== 'undefined') {
window.ChartStreaming = {
createStreamingChartConfig,
addStreamingData,
setStreamingPause,
clearStreamingData,
RealTimeScale,
streamingPlugin
};
}
console.log('📈 Chart.js Streaming Plugin loaded successfully');
});

View File

@ -24,6 +24,12 @@ document.addEventListener('DOMContentLoaded', function () {
initCsvListeners();
initEventListeners();
// 🔑 NUEVO: Inicializar plotManager si existe
if (typeof PlotManager !== 'undefined' && !window.plotManager) {
window.plotManager = new PlotManager();
console.log('📈 PlotManager initialized from main.js');
}
// Configurar actualizaciones periódicas como respaldo
setInterval(updateStatus, 30000); // Cada 30 segundos como respaldo
setInterval(refreshEventLog, 10000); // Cada 10 segundos

View File

@ -3,10 +3,60 @@
* Maneja sesiones de plotting con Chart.js y comunicación con el backend
*/
// 🔧 Global debugging para plots
window.enablePlotDebug = () => {
window.plotDebugEnabled = true;
console.log('📈 Plot debugging enabled. Check console for detailed logs.');
};
window.disablePlotDebug = () => {
window.plotDebugEnabled = false;
console.log('📈 Plot debugging disabled.');
};
// Helper function para logging condicional
function plotDebugLog(...args) {
if (window.plotDebugEnabled) {
console.debug(...args);
}
}
// 🔧 Test function para verificar que todo funciona
window.testPlotSystem = async () => {
console.log('📈 Testing plot system...');
try {
// Test 1: Verificar disponibilidad de variables
const varsResponse = await fetch('/api/plots/variables');
const varsData = await varsResponse.json();
console.log('✅ Available variables:', varsData.available_variables.length);
console.log('✅ Boolean variables for triggers:', varsData.boolean_variables.length);
// Test 2: Verificar plots existentes
const plotsResponse = await fetch('/api/plots');
const plotsData = await plotsResponse.json();
console.log('✅ Existing plot sessions:', plotsData.sessions.length);
// Test 3: Si hay plots, verificar datos
if (plotsData.sessions.length > 0) {
const sessionId = plotsData.sessions[0].session_id;
const dataResponse = await fetch(`/api/plots/${sessionId}/data`);
const dataResult = await dataResponse.json();
console.log(`✅ Plot ${sessionId} has ${dataResult.datasets?.length || 0} datasets with ${dataResult.data_points_count || 0} points`);
}
console.log('📈 Plot system test completed. Enable debug with enablePlotDebug() for detailed logs.');
} catch (error) {
console.error('❌ Plot system test failed:', error);
}
};
class PlotManager {
constructor() {
this.sessions = new Map(); // session_id -> Chart instance
this.updateInterval = null;
this.statusUpdateInterval = null; // 🔑 NUEVO: Para updates de status
this.isInitialized = false;
// Colores para las variables
@ -48,8 +98,32 @@ class PlotManager {
if (data.sessions) {
for (const session of data.sessions) {
// Cargar todas las sesiones (activas e inactivas) y crear tabs
await this.createPlotSessionTab(session.session_id, session);
// 🔑 NUEVO: Obtener configuración completa para cada sesión
try {
const configResponse = await fetch(`/api/plots/${session.session_id}/config`);
const configData = await configResponse.json();
let sessionInfo = session;
// Si tenemos la configuración completa, usar esa información
if (configData.success && configData.config) {
sessionInfo = {
...session,
is_active: configData.config.is_active,
is_paused: configData.config.is_paused,
variables_count: configData.config.variables ? configData.config.variables.length : session.variables_count,
trigger_enabled: configData.config.trigger_enabled,
trigger_variable: configData.config.trigger_variable,
trigger_on_true: configData.config.trigger_on_true
};
}
await this.createPlotSessionTab(session.session_id, sessionInfo);
} catch (configError) {
console.error(`Error loading config for session ${session.session_id}:`, configError);
// Usar información básica si falla la carga de configuración
await this.createPlotSessionTab(session.session_id, session);
}
}
}
} catch (error) {
@ -62,6 +136,11 @@ class PlotManager {
this.updateInterval = setInterval(() => {
this.updateAllSessions();
}, 500);
// 🔑 NUEVO: Actualizar status cada 2 segundos para mantener sincronización
this.statusUpdateInterval = setInterval(() => {
this.updateAllSessionsStatus();
}, 2000);
}
stopAutoUpdate() {
@ -69,6 +148,12 @@ class PlotManager {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
// 🔑 NUEVO: Detener también el update de status
if (this.statusUpdateInterval) {
clearInterval(this.statusUpdateInterval);
this.statusUpdateInterval = null;
}
}
async updateAllSessions() {
@ -79,13 +164,35 @@ class PlotManager {
}
}
// 🔑 NUEVO: Actualizar status de todas las sesiones
async updateAllSessionsStatus() {
const activeSessions = Array.from(this.sessions.keys());
for (const sessionId of activeSessions) {
await this.updateSessionStatus(sessionId);
}
}
async updateSessionData(sessionId) {
try {
// 🚀 NUEVO: Para streaming, el refreshStreamingData maneja la actualización automática
// Esta función ahora es principalmente para compatibilidad y casos especiales
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart) {
return;
}
const response = await fetch(`/api/plots/${sessionId}/data`);
const plotData = await response.json();
if (plotData.datasets) {
// 🔧 DEBUG: Log para troubleshooting
if (plotData.datasets.length > 1) {
plotDebugLog(`📈 Plot ${sessionId}: Manual update ${plotData.datasets.length} variables, ${plotData.data_points_count} total points`);
}
this.updateChart(sessionId, plotData);
} else {
console.warn(`📈 Plot ${sessionId}: No datasets received from API`);
}
} catch (error) {
console.error(`Error updating session ${sessionId}:`, error);
@ -133,91 +240,107 @@ class PlotManager {
document.getElementById('plot-sessions-container').appendChild(container);
// Crear Chart.js
// 🚀 NUEVO: Usar chartjs-plugin-streaming para crear chart
const ctx = document.getElementById(`chart-${sessionId}`).getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0 // Sin animación para mejor performance
},
scales: {
x: {
type: 'time',
time: {
unit: 'second',
displayFormats: {
second: 'HH:mm:ss'
}
},
title: {
display: true,
text: 'Time'
}
},
y: {
title: {
display: true,
text: 'Value'
}
}
},
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
// Configurar streaming con la nueva librería
const streamingConfig = window.ChartStreaming.createStreamingChartConfig({
duration: (config.time_window || 60) * 1000, // Convertir a milisegundos
refresh: 500, // Actualizar cada 500ms
frameRate: 30,
pause: false,
yMin: config.y_min,
yMax: config.y_max,
onRefresh: (chart) => {
// Esta función se llama automáticamente para obtener nuevos datos
this.refreshStreamingData(sessionId, chart);
}
});
this.sessions.set(sessionId, chart);
const chart = new Chart(ctx, streamingConfig);
// Actualizar datos iniciales
this.updateSessionData(sessionId);
this.sessions.set(sessionId, {
chart: chart,
config: config,
lastDataFetch: 0,
datasets: new Map() // Para tracking de datasets por variable
});
console.log(`📈 Created plot session: ${sessionId}`);
// Inicializar datasets para las variables del plot
this.initializeStreamingDatasets(sessionId, config);
console.log(`📈 Created streaming plot session: ${sessionId}`);
}
updateChart(sessionId, plotData) {
const chart = this.sessions.get(sessionId);
if (!chart) return;
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart) {
console.warn(`📈 Plot ${sessionId}: Chart not found in sessions`);
return;
}
// Actualizar datasets
chart.data.datasets = plotData.datasets;
const chart = sessionData.chart;
// Actualizar escalas Y si están definidas
if (plotData.y_min !== undefined || plotData.y_max !== undefined) {
// 🔧 DEBUG: Verificar datos antes de actualizar
if (plotData.datasets && plotData.datasets.length > 0) {
plotDebugLog(`📈 Plot ${sessionId}: Updating streaming chart with ${plotData.datasets.length} datasets`);
plotData.datasets.forEach((dataset, idx) => {
plotDebugLog(` - Variable ${idx + 1}: ${dataset.label} (${dataset.data.length} points)`);
});
}
// 🚀 NUEVO: Para streaming, agregamos nuevos datos en lugar de reemplazar todo
this.updateStreamingData(sessionId, plotData);
// Actualizar escalas Y - mejore el auto-scaling
if (plotData.y_min !== undefined && plotData.y_max !== undefined) {
chart.options.scales.y.min = plotData.y_min;
chart.options.scales.y.max = plotData.y_max;
} else {
// Auto-scaling mejorado cuando no hay límites definidos
chart.options.scales.y.min = undefined;
chart.options.scales.y.max = undefined;
}
// Actualizar contador de puntos
const pointsElement = document.getElementById(`points-${sessionId}`);
if (pointsElement) {
pointsElement.textContent = plotData.data_points_count || 0;
const totalPoints = plotData.data_points_count || 0;
pointsElement.textContent = totalPoints;
// 🔧 DEBUG: Log si el contador no coincide
const calculatedPoints = (plotData.datasets || []).reduce((sum, dataset) => sum + (dataset.data?.length || 0), 0);
if (totalPoints !== calculatedPoints) {
plotDebugLog(`📈 Plot ${sessionId}: Points mismatch - reported: ${totalPoints}, calculated: ${calculatedPoints}`);
}
}
// Actualizar chart
chart.update('none');
// 🔑 NUEVO: Actualizar status visual del plot
if (plotData.is_active !== undefined || plotData.is_paused !== undefined) {
this.updatePlotStats(sessionId, {
variables_count: plotData.datasets ? plotData.datasets.length : 0,
is_active: plotData.is_active,
is_paused: plotData.is_paused,
trigger_enabled: plotData.trigger_enabled,
trigger_variable: plotData.trigger_variable,
trigger_on_true: plotData.trigger_on_true
});
}
// El chart de streaming se actualiza automáticamente, no necesitamos llamar update manualmente
}
async controlPlot(sessionId, action) {
try {
// 🚀 NUEVO: Controlar streaming localmente para algunas acciones
if (action === 'pause') {
this.setStreamingPause(sessionId, true);
} else if (action === 'start') {
this.setStreamingPause(sessionId, false);
} else if (action === 'clear') {
this.clearStreamingData(sessionId);
}
const response = await fetch(`/api/plots/${sessionId}/control`, {
method: 'POST',
headers: {
@ -229,12 +352,17 @@ class PlotManager {
const result = await response.json();
if (result.success) {
// Actualizar UI según la acción
// 🔑 NUEVO: Actualizar status inmediatamente después de la acción
await this.updateSessionStatus(sessionId);
// Para stop, también pausar el streaming
if (action === 'stop') {
this.removeChart(sessionId);
} else {
// Actualizar datos inmediatamente
await this.updateSessionData(sessionId);
this.setStreamingPause(sessionId, true);
}
// Para start, asegurar que el streaming esté activo
if (action === 'start') {
this.setStreamingPause(sessionId, false);
}
showNotification(result.message, 'success');
@ -247,6 +375,31 @@ class PlotManager {
}
}
// 🔑 NUEVO: Método para actualizar solo el status del plot
async updateSessionStatus(sessionId) {
try {
const response = await fetch(`/api/plots/${sessionId}/config`);
const data = await response.json();
if (data.success && data.config) {
this.updatePlotStats(sessionId, data.config);
// 🚀 NUEVO: Actualizar estado de streaming basado en el status del backend
const sessionData = this.sessions.get(sessionId);
if (sessionData) {
// Actualizar configuración local
sessionData.config = data.config;
// Controlar pausa del streaming basado en el estado del plot
const shouldPause = !data.config.is_active || data.config.is_paused;
this.setStreamingPause(sessionId, shouldPause);
}
}
} catch (error) {
console.error(`Error updating session status ${sessionId}:`, error);
}
}
async removePlot(sessionId) {
try {
const response = await fetch(`/api/plots/${sessionId}`, {
@ -269,9 +422,9 @@ class PlotManager {
removeChart(sessionId) {
// Destruir Chart.js
const chart = this.sessions.get(sessionId);
if (chart) {
chart.destroy();
const sessionData = this.sessions.get(sessionId);
if (sessionData && sessionData.chart) {
sessionData.chart.destroy();
this.sessions.delete(sessionId);
}
@ -282,70 +435,208 @@ class PlotManager {
}
}
// 🚀 NUEVAS FUNCIONES PARA STREAMING
/**
* Inicializa los datasets de streaming para las variables del plot
*/
initializeStreamingDatasets(sessionId, config) {
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart || !config.variables) {
return;
}
const chart = sessionData.chart;
const datasets = [];
config.variables.forEach((variable, index) => {
const color = this.getColor(variable, index);
const dataset = {
label: variable,
data: [],
borderColor: color,
backgroundColor: color + '20', // Color con transparencia
borderWidth: 2,
fill: false,
pointRadius: 0,
pointHoverRadius: 3,
tension: 0.1
};
datasets.push(dataset);
sessionData.datasets.set(variable, index);
});
chart.data.datasets = datasets;
chart.update('quiet');
plotDebugLog(`📈 Plot ${sessionId}: Initialized ${datasets.length} streaming datasets`);
}
/**
* Función llamada automáticamente por chartjs-plugin-streaming para obtener nuevos datos
*/
async refreshStreamingData(sessionId, chart) {
try {
// Evitar llamadas muy frecuentes
const sessionData = this.sessions.get(sessionId);
if (!sessionData) return;
const now = Date.now();
if (now - sessionData.lastDataFetch < 400) { // Mínimo 400ms entre llamadas
return;
}
sessionData.lastDataFetch = now;
// Obtener datos del backend
const response = await fetch(`/api/plots/${sessionId}/data`);
const plotData = await response.json();
if (plotData && plotData.datasets) {
// Para streaming, solo agregamos los datos más recientes
this.addLatestDataToStreamingChart(sessionId, plotData);
}
} catch (error) {
console.error(`Error refreshing streaming data for ${sessionId}:`, error);
}
}
/**
* Actualiza los datos de streaming del chart
*/
updateStreamingData(sessionId, plotData) {
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart || !plotData.datasets) {
return;
}
const chart = sessionData.chart;
// Para cada dataset del backend, agregar los nuevos puntos
plotData.datasets.forEach((backendDataset, index) => {
if (chart.data.datasets[index] && backendDataset.data) {
const chartDataset = chart.data.datasets[index];
// Obtener los últimos puntos que no tengamos aún
const existingPoints = chartDataset.data.length;
const newPoints = backendDataset.data.slice(existingPoints);
newPoints.forEach(point => {
window.ChartStreaming.addStreamingData(chart, index, {
x: point.x,
y: point.y
});
});
plotDebugLog(`📈 Plot ${sessionId}: Added ${newPoints.length} new points to dataset ${index}`);
}
});
}
/**
* Agrega solo los datos más recientes al chart de streaming
*/
addLatestDataToStreamingChart(sessionId, plotData) {
const sessionData = this.sessions.get(sessionId);
if (!sessionData || !sessionData.chart || !plotData.datasets) {
return;
}
const chart = sessionData.chart;
const now = Date.now();
// Para cada variable, agregar el punto más reciente
plotData.datasets.forEach((dataset, index) => {
if (dataset.data && dataset.data.length > 0) {
// Obtener el punto más reciente
const latestPoint = dataset.data[dataset.data.length - 1];
if (latestPoint && latestPoint.y !== null && latestPoint.y !== undefined) {
window.ChartStreaming.addStreamingData(chart, index, {
x: now, // Usar timestamp actual para streaming en tiempo real
y: latestPoint.y
});
}
}
});
}
/**
* Controla la pausa/reanudación del streaming
*/
setStreamingPause(sessionId, paused) {
const sessionData = this.sessions.get(sessionId);
if (sessionData && sessionData.chart) {
window.ChartStreaming.setStreamingPause(sessionData.chart, paused);
plotDebugLog(`📈 Plot ${sessionId}: Streaming ${paused ? 'paused' : 'resumed'}`);
}
}
/**
* Limpia todos los datos de streaming
*/
clearStreamingData(sessionId) {
const sessionData = this.sessions.get(sessionId);
if (sessionData && sessionData.chart) {
window.ChartStreaming.clearStreamingData(sessionData.chart);
plotDebugLog(`📈 Plot ${sessionId}: Streaming data cleared`);
}
}
async createPlotSessionTab(sessionId, sessionInfo) {
// Crear tab dinámico para el plot
if (typeof tabManager !== 'undefined') {
tabManager.createPlotTab(sessionId, sessionInfo.name || `Plot ${sessionId}`);
}
// Crear el Chart.js en el canvas del tab
// 🚀 NUEVO: Obtener configuración completa del plot para el streaming
let plotConfig = sessionInfo;
try {
const configResponse = await fetch(`/api/plots/${sessionId}/config`);
const configData = await configResponse.json();
if (configData.success && configData.config) {
plotConfig = configData.config;
}
} catch (error) {
console.warn(`Could not load plot config for ${sessionId}, using basic info`);
}
// Crear el Chart.js con streaming en el canvas del tab
const ctx = document.getElementById(`chart-${sessionId}`).getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
datasets: []
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 0 // Sin animación para mejor performance
},
scales: {
x: {
type: 'time',
time: {
unit: 'second',
displayFormats: {
second: 'HH:mm:ss'
}
},
title: {
display: true,
text: 'Time'
}
},
y: {
title: {
display: true,
text: 'Value'
}
}
},
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
mode: 'index',
intersect: false
}
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false
}
// Configurar streaming con la nueva librería
const streamingConfig = window.ChartStreaming.createStreamingChartConfig({
duration: (plotConfig.time_window || 60) * 1000, // Convertir a milisegundos
refresh: 500, // Actualizar cada 500ms
frameRate: 30,
pause: !plotConfig.is_active, // Pausar si no está activo
yMin: plotConfig.y_min,
yMax: plotConfig.y_max,
onRefresh: (chart) => {
// Esta función se llama automáticamente para obtener nuevos datos
this.refreshStreamingData(sessionId, chart);
}
});
this.sessions.set(sessionId, chart);
const chart = new Chart(ctx, streamingConfig);
this.sessions.set(sessionId, {
chart: chart,
config: plotConfig,
lastDataFetch: 0,
datasets: new Map() // Para tracking de datasets por variable
});
// Inicializar datasets para las variables del plot
if (plotConfig.variables) {
this.initializeStreamingDatasets(sessionId, plotConfig);
}
// Actualizar estadísticas del plot
this.updatePlotStats(sessionId, sessionInfo);
console.log(`📈 Created plot tab and chart for session: ${sessionId}`);
console.log(`📈 Created streaming plot tab and chart for session: ${sessionId}`);
}
updatePlotStats(sessionId, sessionInfo) {
@ -377,17 +668,29 @@ class PlotManager {
const result = await response.json();
if (result.success) {
// Crear tab dinámico y chart
const sessionConfig = {
// 🔑 NUEVO: Obtener configuración real del backend en lugar de hardcodear
const configResponse = await fetch(`/api/plots/${result.session_id}/config`);
const configData = await configResponse.json();
let sessionConfig = {
name: config.name,
variables_count: config.variables.length,
trigger_enabled: config.trigger_enabled,
trigger_variable: config.trigger_variable,
trigger_on_true: config.trigger_on_true,
is_active: false, // Por defecto stopped
is_active: false, // Por defecto stopped hasta que se obtenga del backend
is_paused: false
};
// Si tenemos la configuración real del backend, usarla
if (configData.success && configData.config) {
sessionConfig = {
...sessionConfig,
is_active: configData.config.is_active,
is_paused: configData.config.is_paused
};
}
await this.createPlotSessionTab(result.session_id, sessionConfig);
// Cambiar al sub-tab del nuevo plot
@ -395,6 +698,9 @@ class PlotManager {
tabManager.switchSubTab(`plot-${result.session_id}`);
}
// 🔑 NUEVO: Los plots se auto-inician en el backend, no necesitamos start manual aquí
console.log(`Plot session created and auto-started: ${result.session_id}`);
showNotification(result.message, 'success');
return result.session_id;
} else {
@ -947,6 +1253,9 @@ class PlotManager {
if (config.y_min) config.y_min = parseFloat(config.y_min);
if (config.y_max) config.y_max = parseFloat(config.y_max);
// 🔧 DEBUG: Log configuración del plot
plotDebugLog('📈 Creating plot with config:', config);
try {
let response;
let sessionId = null;
@ -1041,8 +1350,10 @@ class PlotManager {
this.stopAutoUpdate();
// Destruir todos los charts
for (const [sessionId, chart] of this.sessions) {
chart.destroy();
for (const [sessionId, sessionData] of this.sessions) {
if (sessionData && sessionData.chart) {
sessionData.chart.destroy();
}
}
this.sessions.clear();

856
web_test/consola.txt Normal file
View File

@ -0,0 +1,856 @@
plotting.js:20 📈 Plot plot_16: Updating streaming chart with 1 datasets
plotting.js:20 - Variable 1: UR29_Brix (31 points)
plotting.js:20 📈 Plot plot_16: Added 0 new points to dataset 0
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
plotting.js:20 📈 Plot plot_16: Fetching data from backend...
chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object
chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
debug-streaming.js:103 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 32, isActive: true, isPaused: false}
debug-streaming.js:112 ✅ Backend devuelve datos
debug-streaming.js:116 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 32, samplePoint: {…}}
debug-streaming.js:123 ✅ Dataset tiene puntos de datos
debug-streaming.js:130 ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115887307, differenceMs: 6427.700927734375, differenceSec: 6, isRecent: true}
debug-streaming.js:156
5⃣ PROBANDO FUNCIONALIDAD STREAMING...
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887307, y=67.77492245225156
debug-streaming.js:165 🧪 Test de addStreamingData: ✅
debug-streaming.js:170 📊 Puntos después del test: 38
debug-streaming.js:182
6⃣ RESUMEN DEL DIAGNÓSTICO
debug-streaming.js:183 ========================================
debug-streaming.js:186 🎉 No se encontraron errores graves
debug-streaming.js:199
7⃣ PRÓXIMOS PASOS RECOMENDADOS:
debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start"
plotting.js:20 📈 Plot plot_16: Streaming resumed
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object
chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}
debug-streaming.js:103 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 34, isActive: true, isPaused: false}
debug-streaming.js:112 ✅ Backend devuelve datos
debug-streaming.js:116 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 34, samplePoint: {…}}
debug-streaming.js:123 ✅ Dataset tiene puntos de datos
debug-streaming.js:130 ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115887617, differenceMs: 6737.700927734375, differenceSec: 7, isRecent: true}
debug-streaming.js:156
5⃣ PROBANDO FUNCIONALIDAD STREAMING...
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887617, y=11.743880180299548
debug-streaming.js:165 🧪 Test de addStreamingData: ✅
debug-streaming.js:170 📊 Puntos después del test: 39
debug-streaming.js:182
6⃣ RESUMEN DEL DIAGNÓSTICO
debug-streaming.js:183 ========================================
debug-streaming.js:186 🎉 No se encontraron errores graves
debug-streaming.js:199
7⃣ PRÓXIMOS PASOS RECOMENDADOS:
debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start"
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
📈 Plot debugging enabled. Check console for detailed logs.
⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:20 📈 Plot plot_16: Received data: {data_points_count: 35, datasets: Array(1), is_active: true, is_paused: false, last_update: 1753115887.7399745, …}
plotting.js:20 📈 Plot plot_16: Processing 1 datasets for streaming
plotting.js:20 📈 Plot plot_16: Adding 17 new points for UR29_Brix
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115884519.0737, y=44.65407943725586
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115884719.9353, y=46.29325866699219
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115884920.8208, y=47.510704040527344
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885121.4167, y=47.90060806274414
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885323.425, y=47.90060806274414
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885522.0952, y=49.372684478759766
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885724.864, y=50.64583206176758
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115885925.8164, y=50.68561935424805
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886127.653, y=51.82349395751953
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886327.9204, y=51.831451416015625
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886529.75, y=52.46802520751953
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886731.3887, y=52.810184478759766
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115886934.3843, y=53.60590362548828
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887135.6711, y=53.60590362548828
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887337.3464, y=54.2265625
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887538.3455, y=54.2265625
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115887739.9746, y=54.65625
plotting.js:20 📈 Plot plot_16: Updated last timestamp for UR29_Brix to 1753115887739.9746
chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
plotting.js:20 📈 Plot plot_16: Fetching data from backend...
chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object
chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
debug-streaming.js:103 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 37, isActive: true, isPaused: false}
debug-streaming.js:112 ✅ Backend devuelve datos
debug-streaming.js:116 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 37, samplePoint: {…}}
debug-streaming.js:123 ✅ Dataset tiene puntos de datos
debug-streaming.js:130 ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115888236, differenceMs: 7356.700927734375, differenceSec: 7, isRecent: true}
debug-streaming.js:156
5⃣ PROBANDO FUNCIONALIDAD STREAMING...
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115888237, y=23.50854350922291
debug-streaming.js:165 🧪 Test de addStreamingData: ✅
debug-streaming.js:170 📊 Puntos después del test: 57
debug-streaming.js:182
6⃣ RESUMEN DEL DIAGNÓSTICO
debug-streaming.js:183 ========================================
debug-streaming.js:186 🎉 No se encontraron errores graves
debug-streaming.js:199
7⃣ PRÓXIMOS PASOS RECOMENDADOS:
debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start"
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
debug-streaming.js:103 📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 38, isActive: true, isPaused: false}
debug-streaming.js:112 ✅ Backend devuelve datos
debug-streaming.js:116 📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 38, samplePoint: {…}}
debug-streaming.js:123 ✅ Dataset tiene puntos de datos
debug-streaming.js:130 ⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115888548, differenceMs: 7668.700927734375, differenceSec: 8, isRecent: true}
debug-streaming.js:156
5⃣ PROBANDO FUNCIONALIDAD STREAMING...
chartjs-plugin-streaming.js:345 📈 Added point to dataset 0 (UR29_Brix): x=1753115888548, y=4.850139391135455
debug-streaming.js:165 🧪 Test de addStreamingData: ✅
debug-streaming.js:170 📊 Puntos después del test: 58
debug-streaming.js:182
6⃣ RESUMEN DEL DIAGNÓSTICO
debug-streaming.js:183 ========================================
debug-streaming.js:186 🎉 No se encontraron errores graves
debug-streaming.js:199
7⃣ PRÓXIMOS PASOS RECOMENDADOS:
debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start"
chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object
chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:20 📈 Plot plot_16: Updating streaming chart with 1 datasets
plotting.js:20 - Variable 1: UR29_Brix (40 points)
plotting.js:20 📈 Plot plot_16: Added 0 new points to dataset 0
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
chartjs-plugin-streaming.js:75 📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:76 📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
chartjs-plugin-streaming.js:77 📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
plotting.js:20 📈 Plot plot_16: Fetching data from backend...
chartjs-plugin-streaming.js:81 📈 RealTimeScale DEBUG - onRefresh resolved: null object
chartjs-plugin-streaming.js:93 📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}
📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 42, isActive: true, isPaused: false}
✅ Backend devuelve datos
📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 42, samplePoint: {…}}
✅ Dataset tiene puntos de datos
⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115889176, differenceMs: 8296.700927734375, differenceSec: 8, isRecent: true}
5⃣ PROBANDO FUNCIONALIDAD STREAMING...
📈 Added point to dataset 0 (UR29_Brix): x=1753115889176, y=42.83092459112214
🧪 Test de addStreamingData: ✅
📊 Puntos después del test: 59
6⃣ RESUMEN DEL DIAGNÓSTICO
debug-streaming.js:183 ========================================
debug-streaming.js:186 🎉 No se encontraron errores graves
debug-streaming.js:199
7⃣ PRÓXIMOS PASOS RECOMENDADOS:
debug-streaming.js:213 🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start"
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
debug-streaming.js:44 ✅ PlotManager inicializado
debug-streaming.js:45 📊 Sesiones activas: 1
debug-streaming.js:59
3⃣ ANALIZANDO SESIÓN DE PLOT...
debug-streaming.js:65 📈 Sesión encontrada: plot_16
debug-streaming.js:78 Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
debug-streaming.js:85 ✅ Escala realtime configurada correctamente
debug-streaming.js:93 ✅ Streaming habilitado en el chart
debug-streaming.js:98
4⃣ VERIFICANDO DATOS DEL BACKEND...
plotting.js:9 📈 Plot debugging enabled. Check console for detailed logs.
debug-streaming.js:278 ⚡ TEST RÁPIDO DE STREAMING
debug-streaming.js:279 ------------------------------
debug-streaming.js:288 plotManager: ✅ true
debug-streaming.js:288 sessions: ✅ 1
debug-streaming.js:288 chartStreaming: ✅ true
debug-streaming.js:293
🧪 Probando sesión: plot_16
debug-streaming.js:8 🔧 DIAGNÓSTICO DETALLADO DE STREAMING
debug-streaming.js:9 ============================================================
debug-streaming.js:23
1⃣ VERIFICANDO LIBRERÍAS...
debug-streaming.js:30 Chart.js: ✅
debug-streaming.js:31 ChartStreaming: ✅
debug-streaming.js:32 PlotManager: ✅
debug-streaming.js:41
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 43, isActive: true, isPaused: false}
✅ Backend devuelve datos
📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 43, samplePoint: {…}}
✅ Dataset tiene puntos de datos
⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115889485, differenceMs: 8605.700927734375, differenceSec: 9, isRecent: true}
5⃣ PROBANDO FUNCIONALIDAD STREAMING...
📈 Added point to dataset 0 (UR29_Brix): x=1753115889485, y=0.23291564278824506
🧪 Test de addStreamingData: ✅
📊 Puntos después del test: 60
6⃣ RESUMEN DEL DIAGNÓSTICO
========================================
🎉 No se encontraron errores graves
7⃣ PRÓXIMOS PASOS RECOMENDADOS:
🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start"
📈 Plot plot_16: Streaming resumed
📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 RealTimeScale DEBUG - onRefresh resolved: null object
📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📈 Plot debugging enabled. Check console for detailed logs.
⚡ TEST RÁPIDO DE STREAMING
------------------------------
plotManager: ✅ true
sessions: ✅ 1
chartStreaming: ✅ true
🧪 Probando sesión: plot_16
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📈 Plot plot_16: Updating streaming chart with 1 datasets
- Variable 1: UR29_Brix (45 points)
📈 Plot plot_16: Added 0 new points to dataset 0
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📈 Plot debugging enabled. Check console for detailed logs.
⚡ TEST RÁPIDO DE STREAMING
------------------------------
plotManager: ✅ true
sessions: ✅ 1
chartStreaming: ✅ true
🧪 Probando sesión: plot_16
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 Plot plot_16: Fetching data from backend...
📈 RealTimeScale DEBUG - onRefresh resolved: null object
📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}
📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 46, isActive: true, isPaused: false}
✅ Backend devuelve datos
📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 46, samplePoint: {…}}
✅ Dataset tiene puntos de datos
⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115890109, differenceMs: 9229.700927734375, differenceSec: 9, isRecent: true}
5⃣ PROBANDO FUNCIONALIDAD STREAMING...
📈 Added point to dataset 0 (UR29_Brix): x=1753115890109, y=15.728858368162268
🧪 Test de addStreamingData: ✅
📊 Puntos después del test: 61
6⃣ RESUMEN DEL DIAGNÓSTICO
========================================
🎉 No se encontraron errores graves
7⃣ PRÓXIMOS PASOS RECOMENDADOS:
🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start"
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📈 Plot debugging enabled. Check console for detailed logs.
⚡ TEST RÁPIDO DE STREAMING
------------------------------
plotManager: ✅ true
sessions: ✅ 1
chartStreaming: ✅ true
🧪 Probando sesión: plot_16
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📊 Respuesta del backend: {success: true, datasets: 1, totalPoints: 48, isActive: true, isPaused: false}
✅ Backend devuelve datos
📈 Primer dataset: {label: 'UR29_Brix', dataPoints: 48, samplePoint: {…}}
✅ Dataset tiene puntos de datos
⏰ Análisis de timestamps: {firstPointTime: 1753115880879.299, currentTime: 1753115890413, differenceMs: 9533.700927734375, differenceSec: 10, isRecent: true}
5⃣ PROBANDO FUNCIONALIDAD STREAMING...
📈 Added point to dataset 0 (UR29_Brix): x=1753115890413, y=93.73804066063487
🧪 Test de addStreamingData: ✅
📊 Puntos después del test: 62
6⃣ RESUMEN DEL DIAGNÓSTICO
========================================
🎉 No se encontraron errores graves
7⃣ PRÓXIMOS PASOS RECOMENDADOS:
🔧 4. Si persiste: plotManager.controlPlot("plot_16", "stop") y luego "start"
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📈 Plot debugging enabled. Check console for detailed logs.
⚡ TEST RÁPIDO DE STREAMING
------------------------------
plotManager: ✅ true
sessions: ✅ 1
chartStreaming: ✅ true
🧪 Probando sesión: plot_16
🔧 DIAGNÓSTICO DETALLADO DE STREAMING
============================================================
1⃣ VERIFICANDO LIBRERÍAS...
Chart.js: ✅
ChartStreaming: ✅
PlotManager: ✅
2⃣ VERIFICANDO PLOT MANAGER...
✅ PlotManager inicializado
📊 Sesiones activas: 1
3⃣ ANALIZANDO SESIÓN DE PLOT...
📈 Sesión encontrada: plot_16
Chart Config: {hasChart: true, chartType: 'line', scaleType: 'realtime', scaleConstructor: 'RealTimeScale', streamingEnabled: true, …}
✅ Escala realtime configurada correctamente
✅ Streaming habilitado en el chart
4⃣ VERIFICANDO DATOS DEL BACKEND...
📈 RealTimeScale DEBUG - scaleOptions: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 RealTimeScale DEBUG - me.options: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 RealTimeScale DEBUG - me.options.realtime: Proxy(Object) {_cacheable: false, _proxy: Proxy(Object), _context: {…}, _subProxy: undefined, _stack: Set(0), …}
📈 RealTimeScale DEBUG - onRefresh resolved: null object
📈 RealTimeScale initialized: {duration: 60000, refresh: 500, pause: false, hasOnRefresh: false}