Files
controls-web/controls-rework/test/HMI/matrix.php
2026-02-17 09:29:34 -06:00

2892 lines
98 KiB
PHP

<?php
// filepath: v:\controls\test\HMI\correlation_matrix.php
include "../../session.php";
include "../../userAccess.php";
// Database connection
$servername = "192.168.0.13";
$username = "opce";
$password = "opcelasuca";
$dbname = "archive";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
if (isset($_POST['action'])) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Database connection failed: ' . $e->getMessage()]);
exit;
}
die("Connection failed: " . $e->getMessage());
}
// Handle AJAX requests
if (isset($_POST['action'])) {
while (ob_get_level()) {
ob_end_clean();
}
ob_start();
header('Content-Type: application/json');
try {
switch ($_POST['action']) {
case 'get_available_tags':
$stmt = $pdo->prepare("
SELECT DISTINCT n.name,
COUNT(*) as record_count,
MIN(h.TimeStamp) as earliest_date,
MAX(h.TimeStamp) as latest_date
FROM id_names n
INNER JOIN historicaldata h ON n.idnumber = h.ID
WHERE n.name IS NOT NULL
AND n.name != ''
AND h.Value IS NOT NULL
AND h.TimeStamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY n.name
HAVING record_count >= 100
ORDER BY record_count DESC, n.name ASC
");
$stmt->execute();
$tags = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'tags' => $tags,
'total_count' => count($tags)
]);
break;
case 'calculate_correlation_matrix':
$selectedTags = json_decode($_POST['tags'] ?? '[]', true);
$timeRange = (int)($_POST['time_range'] ?? 24);
$samplingInterval = (int)($_POST['sampling_interval'] ?? 10);
$correlationMethod = $_POST['correlation_method'] ?? 'pearson';
$lagAnalysis = (bool)($_POST['lag_analysis'] ?? false);
$maxLag = (int)($_POST['max_lag'] ?? 60);
if (empty($selectedTags) || !is_array($selectedTags) || count($selectedTags) < 2) {
throw new Exception('Please select at least 2 tags for correlation analysis');
}
if (count($selectedTags) > 20) {
throw new Exception('Maximum 20 tags allowed for performance reasons');
}
// Get synchronized data for all selected tags
$correlationData = getSynchronizedData($pdo, $selectedTags, $timeRange, $samplingInterval);
if (empty($correlationData)) {
throw new Exception('No synchronized data found for the selected tags and time period');
}
// Calculate correlation matrix
$correlationMatrix = calculateCorrelationMatrix($correlationData, $correlationMethod);
// Calculate lag correlations if requested
$lagCorrelations = [];
if ($lagAnalysis) {
$lagCorrelations = calculateLagCorrelations($correlationData, $maxLag, $correlationMethod);
}
// Calculate statistical significance
$significanceMatrix = calculateSignificanceMatrix($correlationData, $correlationMatrix);
// Generate insights
$insights = generateCorrelationInsights($correlationMatrix, $selectedTags, $significanceMatrix);
echo json_encode([
'success' => true,
'correlation_matrix' => $correlationMatrix,
'lag_correlations' => $lagCorrelations,
'significance_matrix' => $significanceMatrix,
'insights' => $insights,
'data_points' => count($correlationData),
'tags' => $selectedTags,
'time_range' => $timeRange,
'method' => $correlationMethod
]);
break;
case 'get_scatter_plot_data':
$tagX = $_POST['tag_x'] ?? '';
$tagY = $_POST['tag_y'] ?? '';
$timeRange = (int)($_POST['time_range'] ?? 24);
$maxPoints = (int)($_POST['max_points'] ?? 1000);
if (empty($tagX) || empty($tagY)) {
throw new Exception('Both X and Y tags must be specified');
}
$scatterData = getScatterPlotData($pdo, $tagX, $tagY, $timeRange, $maxPoints);
echo json_encode([
'success' => true,
'scatter_data' => $scatterData,
'tag_x' => $tagX,
'tag_y' => $tagY
]);
break;
case 'export_correlation_data':
$selectedTags = json_decode($_POST['tags'] ?? '[]', true);
$timeRange = (int)($_POST['time_range'] ?? 24);
$format = $_POST['format'] ?? 'csv';
if (empty($selectedTags)) {
throw new Exception('No tags selected for export');
}
$exportData = getSynchronizedData($pdo, $selectedTags, $timeRange, 5); // 5-minute intervals for export
if ($format === 'csv') {
$csv = exportToCSV($exportData, $selectedTags);
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="correlation_data_' . date('Y-m-d_H-i-s') . '.csv"');
echo $csv;
exit;
}
echo json_encode([
'success' => true,
'message' => 'Export format not supported'
]);
break;
default:
throw new Exception('Invalid action specified');
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
} catch (Error $e) {
echo json_encode(['success' => false, 'error' => 'PHP Error: ' . $e->getMessage()]);
}
ob_end_flush();
exit;
}
// Helper function to get synchronized data for correlation analysis
function getSynchronizedData($pdo, $tags, $timeRange, $samplingInterval) {
$placeholders = str_repeat('?,', count($tags) - 1) . '?';
// Create time buckets for synchronization
$sql = "
SELECT
FLOOR(UNIX_TIMESTAMP(h.TimeStamp) / (? * 60)) * (? * 60) as time_bucket,
n.name as tag_name,
AVG(h.Value) as avg_value
FROM historicaldata h
INNER JOIN id_names n ON h.ID = n.idnumber
WHERE n.name IN ($placeholders)
AND h.TimeStamp >= DATE_SUB(NOW(), INTERVAL ? HOUR)
AND h.Value IS NOT NULL
GROUP BY time_bucket, n.name
HAVING COUNT(*) >= 1
ORDER BY time_bucket ASC
";
$params = array_merge([$samplingInterval, $samplingInterval], $tags, [$timeRange]);
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rawData = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Organize data by time bucket
$bucketData = [];
foreach ($rawData as $row) {
$bucket = $row['time_bucket'];
$tag = $row['tag_name'];
$value = (float)$row['avg_value'];
if (!isset($bucketData[$bucket])) {
$bucketData[$bucket] = [];
}
$bucketData[$bucket][$tag] = $value;
}
// Filter buckets that have data for all tags
$synchronizedData = [];
foreach ($bucketData as $bucket => $data) {
if (count($data) === count($tags)) {
$row = ['timestamp' => $bucket];
foreach ($tags as $tag) {
$row[$tag] = $data[$tag];
}
$synchronizedData[] = $row;
}
}
return $synchronizedData;
}
// Calculate correlation matrix
function calculateCorrelationMatrix($data, $method = 'pearson') {
if (empty($data)) return [];
$tags = array_keys($data[0]);
$tags = array_filter($tags, function($tag) { return $tag !== 'timestamp'; });
$matrix = [];
foreach ($tags as $tagX) {
$matrix[$tagX] = [];
foreach ($tags as $tagY) {
if ($tagX === $tagY) {
$matrix[$tagX][$tagY] = 1.0;
} else {
$valuesX = array_column($data, $tagX);
$valuesY = array_column($data, $tagY);
switch ($method) {
case 'spearman':
$correlation = calculateSpearmanCorrelation($valuesX, $valuesY);
break;
case 'kendall':
$correlation = calculateKendallCorrelation($valuesX, $valuesY);
break;
default: // pearson
$correlation = calculatePearsonCorrelation($valuesX, $valuesY);
break;
}
$matrix[$tagX][$tagY] = $correlation;
}
}
}
return $matrix;
}
// Calculate Pearson correlation coefficient
function calculatePearsonCorrelation($x, $y) {
$n = count($x);
if ($n < 2) return 0;
$sumX = array_sum($x);
$sumY = array_sum($y);
$sumXY = 0;
$sumX2 = 0;
$sumY2 = 0;
for ($i = 0; $i < $n; $i++) {
$sumXY += $x[$i] * $y[$i];
$sumX2 += $x[$i] * $x[$i];
$sumY2 += $y[$i] * $y[$i];
}
$numerator = $n * $sumXY - $sumX * $sumY;
$denominator = sqrt(($n * $sumX2 - $sumX * $sumX) * ($n * $sumY2 - $sumY * $sumY));
if ($denominator == 0) return 0;
return $numerator / $denominator;
}
// Calculate Spearman rank correlation
function calculateSpearmanCorrelation($x, $y) {
$rankX = array_values(calculateRanks($x));
$rankY = array_values(calculateRanks($y));
return calculatePearsonCorrelation($rankX, $rankY);
}
// Calculate ranks for Spearman correlation
function calculateRanks($values) {
$indexed = [];
foreach ($values as $index => $value) {
$indexed[] = ['value' => $value, 'index' => $index];
}
usort($indexed, function($a, $b) {
return $a['value'] <=> $b['value'];
});
$ranks = [];
for ($i = 0; $i < count($indexed); $i++) {
$ranks[$indexed[$i]['index']] = $i + 1;
}
return $ranks;
}
// Calculate Kendall's tau correlation (simplified version)
function calculateKendallCorrelation($x, $y) {
$n = count($x);
if ($n < 2) return 0;
$concordant = 0;
$discordant = 0;
for ($i = 0; $i < $n - 1; $i++) {
for ($j = $i + 1; $j < $n; $j++) {
$signX = ($x[$j] - $x[$i]) <=> 0;
$signY = ($y[$j] - $y[$i]) <=> 0;
if ($signX * $signY > 0) {
$concordant++;
} elseif ($signX * $signY < 0) {
$discordant++;
}
}
}
$totalPairs = $n * ($n - 1) / 2;
if ($totalPairs == 0) return 0;
return ($concordant - $discordant) / $totalPairs;
}
// Calculate lag correlations
function calculateLagCorrelations($data, $maxLag, $method = 'pearson') {
if (empty($data) || $maxLag <= 0) return [];
$tags = array_keys($data[0]);
$tags = array_filter($tags, function($tag) { return $tag !== 'timestamp'; });
$lagCorrelations = [];
foreach ($tags as $tagX) {
foreach ($tags as $tagY) {
if ($tagX === $tagY) continue;
$valuesX = array_column($data, $tagX);
$valuesY = array_column($data, $tagY);
$bestCorrelation = 0;
$bestLag = 0;
for ($lag = 0; $lag <= min($maxLag, count($valuesX) - 10); $lag++) {
$laggedX = array_slice($valuesX, $lag);
$laggedY = array_slice($valuesY, 0, count($laggedX));
if (count($laggedX) < 10) break;
$correlation = calculatePearsonCorrelation($laggedX, $laggedY);
if (abs($correlation) > abs($bestCorrelation)) {
$bestCorrelation = $correlation;
$bestLag = $lag;
}
}
$lagCorrelations[] = [
'tag_x' => $tagX,
'tag_y' => $tagY,
'correlation' => $bestCorrelation,
'lag' => $bestLag,
'lag_minutes' => $bestLag * 10 // Assuming 10-minute intervals
];
}
}
// Sort by absolute correlation strength
usort($lagCorrelations, function($a, $b) {
return abs($b['correlation']) <=> abs($a['correlation']);
});
return array_slice($lagCorrelations, 0, 20); // Return top 20
}
// Calculate statistical significance
function calculateSignificanceMatrix($data, $correlationMatrix) {
$n = count($data);
$significanceMatrix = [];
foreach ($correlationMatrix as $tagX => $row) {
$significanceMatrix[$tagX] = [];
foreach ($row as $tagY => $correlation) {
if ($tagX === $tagY) {
$significanceMatrix[$tagX][$tagY] = 0; // Perfect correlation, no p-value
} else {
// Calculate t-statistic and p-value
if (abs($correlation) >= 1.0) {
$pValue = 0;
} else {
$tStat = $correlation * sqrt(($n - 2) / (1 - $correlation * $correlation));
$pValue = 2 * (1 - getTDistributionCDF(abs($tStat), $n - 2));
}
$significanceMatrix[$tagX][$tagY] = $pValue;
}
}
}
return $significanceMatrix;
}
// Simplified t-distribution CDF approximation
function getTDistributionCDF($t, $df) {
if ($df <= 0) return 0.5;
// Simple approximation for large degrees of freedom
if ($df >= 30) {
return 0.5 * (1 + erf($t / sqrt(2)));
}
// Very simplified approximation for smaller df
$x = $df / ($df + $t * $t);
return 1 - 0.5 * pow($x, $df / 2);
}
// Error function approximation
function erf($x) {
$sign = ($x >= 0) ? 1 : -1;
$x = abs($x);
$a1 = 0.254829592;
$a2 = -0.284496736;
$a3 = 1.421413741;
$a4 = -1.453152027;
$a5 = 1.061405429;
$p = 0.3275911;
$t = 1.0 / (1.0 + $p * $x);
$y = 1.0 - (((($a5 * $t + $a4) * $t) + $a3) * $t + $a2) * $t + $a1) * $t * exp(-$x * $x);
return $sign * $y;
}
// Generate correlation insights
function generateCorrelationInsights($correlationMatrix, $tags, $significanceMatrix) {
$insights = [];
// Find strongest positive correlations
$strongPositive = [];
$strongNegative = [];
$weakCorrelations = [];
foreach ($correlationMatrix as $tagX => $row) {
foreach ($row as $tagY => $correlation) {
if ($tagX === $tagY) continue;
$pValue = $significanceMatrix[$tagX][$tagY];
$significant = $pValue < 0.05;
$pair = [$tagX, $tagY];
sort($pair); // Avoid duplicates
$pairKey = implode(' - ', $pair);
if ($correlation > 0.7 && $significant) {
$strongPositive[$pairKey] = [
'correlation' => $correlation,
'p_value' => $pValue,
'tags' => $pair
];
} elseif ($correlation < -0.7 && $significant) {
$strongNegative[$pairKey] = [
'correlation' => $correlation,
'p_value' => $pValue,
'tags' => $pair
];
} elseif (abs($correlation) < 0.1) {
$weakCorrelations[$pairKey] = [
'correlation' => $correlation,
'p_value' => $pValue,
'tags' => $pair
];
}
}
}
// Sort by correlation strength
uasort($strongPositive, function($a, $b) { return $b['correlation'] <=> $a['correlation']; });
uasort($strongNegative, function($a, $b) { return $a['correlation'] <=> $b['correlation']; });
$insights['strong_positive'] = array_slice($strongPositive, 0, 5, true);
$insights['strong_negative'] = array_slice($strongNegative, 0, 5, true);
$insights['weak_correlations'] = array_slice($weakCorrelations, 0, 5, true);
// Summary statistics
$allCorrelations = [];
foreach ($correlationMatrix as $tagX => $row) {
foreach ($row as $tagY => $correlation) {
if ($tagX !== $tagY) {
$allCorrelations[] = abs($correlation);
}
}
}
$insights['summary'] = [
'total_pairs' => count($allCorrelations) / 2, // Divide by 2 to avoid counting each pair twice
'average_correlation' => array_sum($allCorrelations) / count($allCorrelations),
'max_correlation' => max($allCorrelations),
'highly_correlated_pairs' => count($strongPositive) + count($strongNegative)
];
return $insights;
}
// Get scatter plot data for two specific tags
function getScatterPlotData($pdo, $tagX, $tagY, $timeRange, $maxPoints) {
$stmt = $pdo->prepare("
SELECT
hx.Value as x_value,
hy.Value as y_value,
hx.TimeStamp
FROM historicaldata hx
INNER JOIN id_names nx ON hx.ID = nx.idnumber
INNER JOIN historicaldata hy ON hx.TimeStamp = hy.TimeStamp
INNER JOIN id_names ny ON hy.ID = ny.idnumber
WHERE nx.name = ?
AND ny.name = ?
AND hx.TimeStamp >= DATE_SUB(NOW(), INTERVAL ? HOUR)
AND hx.Value IS NOT NULL
AND hy.Value IS NOT NULL
ORDER BY hx.TimeStamp DESC
LIMIT ?
");
$stmt->execute([$tagX, $tagY, $timeRange, $maxPoints]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Export correlation data to CSV
function exportToCSV($data, $tags) {
if (empty($data)) return '';
$csv = 'Timestamp,' . implode(',', $tags) . "\n";
foreach ($data as $row) {
$csvRow = [date('Y-m-d H:i:s', $row['timestamp'])];
foreach ($tags as $tag) {
$csvRow[] = $row[$tag] ?? '';
}
$csv .= implode(',', $csvRow) . "\n";
}
return $csv;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LASUCA Controls - Correlation Matrix Analysis</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7.8.5/dist/d3.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: #fff;
min-height: 100vh;
}
.dashboard-header {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(15px);
padding: 25px;
text-align: center;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.dashboard-header h1 {
color: #fff;
font-size: 2.8rem;
margin: 0;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.dashboard-header .subtitle {
color: #ffd700;
font-size: 1.3rem;
margin-top: 10px;
font-weight: 400;
}
.container {
max-width: 1900px;
margin: 0 auto;
padding: 25px;
}
.analysis-layout {
display: grid;
grid-template-columns: 350px 1fr;
gap: 25px;
height: calc(100vh - 140px);
}
/* Control Panel */
.control-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 25px;
border: 1px solid rgba(255, 255, 255, 0.2);
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.control-section {
margin-bottom: 30px;
}
.control-section h3 {
color: #ffd700;
font-size: 1.2rem;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 2px solid rgba(255, 215, 0, 0.3);
padding-bottom: 8px;
}
.tag-selection {
max-height: 300px;
overflow-y: auto;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
}
.tag-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.tag-item:hover {
background: rgba(255, 255, 255, 0.1);
border-radius: 6px;
}
.tag-item:last-child {
border-bottom: none;
}
.tag-item input[type="checkbox"] {
width: 18px;
height: 18px;
margin-right: 12px;
accent-color: #ffd700;
}
.tag-item label {
cursor: pointer;
flex: 1;
font-size: 0.9rem;
display: flex;
flex-direction: column;
}
.tag-name {
color: #fff;
font-weight: 600;
}
.tag-stats {
color: rgba(255, 255, 255, 0.7);
font-size: 0.75rem;
margin-top: 2px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #fff;
font-size: 0.95rem;
}
.form-group select,
.form-group input {
width: 100%;
padding: 12px;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
color: #fff;
font-size: 0.9rem;
transition: all 0.3s ease;
}
.form-group select:focus,
.form-group input:focus {
outline: none;
border-color: #ffd700;
box-shadow: 0 0 0 3px rgba(255, 215, 0, 0.2);
}
.form-group select option {
background: #2a5298;
color: #fff;
}
/* Action Buttons */
.action-buttons {
display: flex;
flex-direction: column;
gap: 12px;
}
button {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
color: white;
border: none;
padding: 14px 20px;
border-radius: 12px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
button:disabled {
background: rgba(255, 255, 255, 0.2);
cursor: not-allowed;
transform: none;
}
button.analyze-btn {
background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
}
button.export-btn {
background: linear-gradient(135deg, #0984e3 0%, #74b9ff 100%);
}
button.clear-btn {
background: linear-gradient(135deg, #e17055 0%, #d63031 100%);
}
/* Main Analysis Area */
.analysis-content {
display: grid;
grid-template-rows: auto 1fr;
gap: 20px;
}
/* Results Header */
.results-header {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 15px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex;
justify-content: space-between;
align-items: center;
}
.results-title {
color: #ffd700;
font-size: 1.5rem;
font-weight: 700;
}
.results-stats {
display: flex;
gap: 20px;
font-size: 0.9rem;
}
.stat-item {
text-align: center;
}
.stat-value {
display: block;
font-size: 1.4rem;
font-weight: 700;
color: #4ecdc4;
}
.stat-label {
color: rgba(255, 255, 255, 0.8);
font-size: 0.8rem;
}
/* Analysis Tabs */
.analysis-tabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
height: 100%;
}
.tab-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 25px;
border: 1px solid rgba(255, 255, 255, 0.2);
overflow: hidden;
display: flex;
flex-direction: column;
}
.tab-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
padding-bottom: 15px;
}
.tab-title {
color: #ffd700;
font-size: 1.3rem;
font-weight: 600;
}
/* Correlation Matrix */
.matrix-container {
flex: 1;
overflow: auto;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
}
.correlation-matrix {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.correlation-matrix th {
background: rgba(255, 255, 255, 0.2);
color: #fff;
padding: 12px 8px;
text-align: center;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.correlation-matrix td {
padding: 8px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.1);
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.correlation-matrix td:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.05);
}
/* Correlation color coding */
.corr-strong-positive { background: rgba(46, 204, 113, 0.8); color: #fff; }
.corr-moderate-positive { background: rgba(52, 152, 219, 0.6); color: #fff; }
.corr-weak-positive { background: rgba(52, 152, 219, 0.3); color: #fff; }
.corr-neutral { background: rgba(255, 255, 255, 0.1); color: #fff; }
.corr-weak-negative { background: rgba(231, 76, 60, 0.3); color: #fff; }
.corr-moderate-negative { background: rgba(231, 76, 60, 0.6); color: #fff; }
.corr-strong-negative { background: rgba(231, 76, 60, 0.8); color: #fff; }
/* Insights Panel */
.insights-container {
flex: 1;
overflow-y: auto;
}
.insight-section {
margin-bottom: 25px;
}
.insight-section h4 {
color: #4ecdc4;
font-size: 1.1rem;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.insight-list {
display: grid;
gap: 10px;
}
.insight-item {
background: rgba(255, 255, 255, 0.1);
padding: 12px;
border-radius: 10px;
border-left: 4px solid;
transition: all 0.3s ease;
}
.insight-item:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateX(5px);
}
.insight-positive { border-left-color: #00b894; }
.insight-negative { border-left-color: #e17055; }
.insight-neutral { border-left-color: #74b9ff; }
.insight-tags {
font-weight: 600;
font-size: 0.95rem;
}
.insight-value {
color: #ffd700;
font-weight: 700;
}
.insight-description {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.8);
margin-top: 4px;
}
/* Scatter Plot Container */
.scatter-container {
flex: 1;
position: relative;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
overflow: hidden;
}
.scatter-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.scatter-controls select {
flex: 1;
padding: 8px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 6px;
background: rgba(255, 255, 255, 0.1);
color: #fff;
font-size: 0.85rem;
}
/* Loading and Status */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-spinner {
width: 80px;
height: 80px;
border: 6px solid rgba(255, 255, 255, 0.3);
border-top: 6px solid #ffd700;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.status-message {
padding: 15px 20px;
margin-bottom: 20px;
border-radius: 12px;
font-weight: 600;
text-align: center;
}
.status-message.success {
background: rgba(0, 184, 148, 0.2);
border: 2px solid rgba(0, 184, 148, 0.5);
color: #00b894;
}
.status-message.error {
background: rgba(225, 112, 85, 0.2);
border: 2px solid rgba(225, 112, 85, 0.5);
color: #e17055;
}
.status-message.info {
background: rgba(116, 185, 255, 0.2);
border: 2px solid rgba(116, 185, 255, 0.5);
color: #74b9ff;
}
/* Legend */
.correlation-legend {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 15px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.8rem;
}
.legend-color {
width: 20px;
height: 12px;
border-radius: 3px;
}
/* Responsive Design */
@media (max-width: 1400px) {
.analysis-layout {
grid-template-columns: 300px 1fr;
}
.analysis-tabs {
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
}
@media (max-width: 1000px) {
.analysis-layout {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
}
.control-panel {
max-height: 400px;
}
}
/* Custom Scrollbars */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 215, 0, 0.5);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 215, 0, 0.7);
}
</style>
</head>
<body>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner"></div>
</div>
<!-- Header -->
<div class="dashboard-header">
<h1>🔗 LASUCA Controls - Multi-Variable Correlation Matrix</h1>
<p class="subtitle">Discover Hidden Relationships Between Process Variables</p>
</div>
<div class="container">
<!-- Status Messages -->
<div id="statusMessages"></div>
<div class="analysis-layout">
<!-- Control Panel -->
<div class="control-panel">
<!-- Tag Selection -->
<div class="control-section">
<h3>🏷️ Select Variables</h3>
<div class="tag-selection" id="tagSelection">
<div style="text-align: center; padding: 20px; color: rgba(255,255,255,0.6);">
Loading available tags...
</div>
</div>
<div class="action-buttons" style="margin-top: 15px;">
<button onclick="selectAllTags()">Select All</button>
<button onclick="clearAllTags()" class="clear-btn">Clear All</button>
</div>
</div>
<!-- Analysis Parameters -->
<div class="control-section">
<h3>⚙️ Analysis Settings</h3>
<div class="form-group">
<label for="timeRange">Time Range:</label>
<select id="timeRange">
<option value="1">Last Hour</option>
<option value="6">Last 6 Hours</option>
<option value="24" selected>Last 24 Hours</option>
<option value="72">Last 3 Days</option>
<option value="168">Last Week</option>
<option value="720">Last Month</option>
</select>
</div>
<div class="form-group">
<label for="samplingInterval">Sampling Interval:</label>
<select id="samplingInterval">
<option value="1">1 Minute</option>
<option value="5">5 Minutes</option>
<option value="10" selected>10 Minutes</option>
<option value="30">30 Minutes</option>
<option value="60">1 Hour</option>
</select>
</div>
<div class="form-group">
<label for="correlationMethod">Correlation Method:</label>
<select id="correlationMethod">
<option value="pearson" selected>Pearson (Linear)</option>
<option value="spearman">Spearman (Rank)</option>
<option value="kendall">Kendall's Tau</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="lagAnalysis" style="width: auto; margin-right: 8px;">
Enable Lag Analysis
</label>
</div>
<div class="form-group" id="maxLagGroup" style="display: none;">
<label for="maxLag">Maximum Lag (intervals):</label>
<input type="number" id="maxLag" value="6" min="1" max="100">
</div>
</div>
<!-- Action Buttons -->
<div class="control-section">
<h3>🚀 Actions</h3>
<div class="action-buttons">
<button onclick="calculateCorrelations()" class="analyze-btn" id="analyzeBtn">
📊 Calculate Correlations
</button>
<button onclick="exportResults()" class="export-btn" id="exportBtn" disabled>
📁 Export Data
</button>
<button onclick="clearResults()" class="clear-btn">
🗑️ Clear Results
</button>
</div>
</div>
</div>
<!-- Analysis Content -->
<div class="analysis-content">
<!-- Results Header -->
<div class="results-header" id="resultsHeader" style="display: none;">
<div class="results-title">Correlation Analysis Results</div>
<div class="results-stats">
<div class="stat-item">
<span class="stat-value" id="dataPointsCount">0</span>
<span class="stat-label">Data Points</span>
</div>
<div class="stat-item">
<span class="stat-value" id="variablesCount">0</span>
<span class="stat-label">Variables</span>
</div>
<div class="stat-item">
<span class="stat-value" id="strongCorrelationsCount">0</span>
<span class="stat-label">Strong Correlations</span>
</div>
</div>
</div>
<!-- Analysis Tabs -->
<div class="analysis-tabs">
<!-- Correlation Matrix Tab -->
<div class="tab-panel">
<div class="tab-header">
<div class="tab-title">🎯 Correlation Matrix</div>
</div>
<div class="matrix-container" id="matrixContainer">
<div style="text-align: center; padding: 50px; color: rgba(255,255,255,0.6);">
Select variables and click "Calculate Correlations" to see the matrix
</div>
</div>
<div class="correlation-legend">
<div class="legend-item">
<div class="legend-color corr-strong-positive"></div>
<span>Strong Positive (>0.7)</span>
</div>
<div class="legend-item">
<div class="legend-color corr-moderate-positive"></div>
<span>Moderate Positive (0.3-0.7)</span>
</div>
<div class="legend-item">
<div class="legend-color corr-neutral"></div>
<span>Weak (-0.3 to 0.3)</span>
</div>
<div class="legend-item">
<div class="legend-color corr-moderate-negative"></div>
<span>Moderate Negative (-0.7 to -0.3)</span>
</div>
<div class="legend-item">
<div class="legend-color corr-strong-negative"></div>
<span>Strong Negative (<-0.7)</span>
</div>
</div>
</div>
<!-- Insights Tab -->
<div class="tab-panel">
<div class="tab-header">
<div class="tab-title">💡 Key Insights</div>
</div>
<div class="insights-container" id="insightsContainer">
<div style="text-align: center; padding: 50px; color: rgba(255,255,255,0.6);">
Analysis insights will appear here after calculation
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Global variables
let availableTags = [];
let correlationMatrix = {};
let lagCorrelations = [];
let currentInsights = {};
let selectedTags = [];
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
loadAvailableTags();
setupEventListeners();
});
// Setup event listeners
function setupEventListeners() {
document.getElementById('lagAnalysis').addEventListener('change', function() {
document.getElementById('maxLagGroup').style.display = this.checked ? 'block' : 'none';
});
}
// Load available tags
async function loadAvailableTags() {
try {
showLoading(true);
const formData = new FormData();
formData.append('action', 'get_available_tags');
const response = await fetch(window.location.href, {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
availableTags = data.tags;
renderTagSelection();
showStatus(`${data.total_count} variables loaded with sufficient data`, 'success');
} else {
throw new Error(data.error || 'Failed to load available tags');
}
} catch (error) {
console.error('Error loading tags:', error);
showStatus('Failed to load available variables: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
// Render tag selection interface
function renderTagSelection() {
const container = document.getElementById('tagSelection');
if (availableTags.length === 0) {
container.innerHTML = '<div style="text-align: center; padding: 20px; color: rgba(255,255,255,0.6);">No variables with sufficient data found</div>';
return;
}
let html = '';
availableTags.forEach((tag, index) => {
const recordCount = parseInt(tag.record_count);
const timeSpan = Math.round((new Date(tag.latest_date) - new Date(tag.earliest_date)) / (1000 * 60 * 60 * 24));
html += `
<div class="tag-item">
<input type="checkbox" id="tag_${index}" value="${tag.name}" onchange="updateSelectedTags()">
<label for="tag_${index}">
<div class="tag-name">${tag.name}</div>
<div class="tag-stats">${recordCount.toLocaleString()} records • ${timeSpan} days</div>
</label>
</div>
`;
});
container.innerHTML = html;
}
// Update selected tags array
function updateSelectedTags() {
const checkboxes = document.querySelectorAll('#tagSelection input[type="checkbox"]:checked');
selectedTags = Array.from(checkboxes).map(cb => cb.value);
}
// Select/clear all tags
function selectAllTags() {
const checkboxes = document.querySelectorAll('#tagSelection input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = true);
updateSelectedTags();
}
function clearAllTags() {
const checkboxes = document.querySelectorAll('#tagSelection input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = false);
updateSelectedTags();
}
// Calculate correlations
async function calculateCorrelations() {
try {
updateSelectedTags();
if (selectedTags.length < 2) {
showStatus('Please select at least 2 variables for correlation analysis', 'error');
return;
}
if (selectedTags.length > 20) {
showStatus('Maximum 20 variables allowed for performance reasons', 'error');
return;
}
showLoading(true);
document.getElementById('analyzeBtn').disabled = true;
const formData = new FormData();
formData.append('action', 'calculate_correlation_matrix');
formData.append('tags', JSON.stringify(selectedTags));
formData.append('time_range', document.getElementById('timeRange').value);
formData.append('sampling_interval', document.getElementById('samplingInterval').value);
formData.append('correlation_method', document.getElementById('correlationMethod').value);
formData.append('lag_analysis', document.getElementById('lagAnalysis').checked);
formData.append('max_lag', document.getElementById('maxLag').value);
const response = await fetch(window.location.href, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
correlationMatrix = result.correlation_matrix;
lagCorrelations = result.lag_correlations || [];
currentInsights = result.insights;
displayCorrelationMatrix(result.correlation_matrix, result.significance_matrix);
displayInsights(result.insights, result.lag_correlations);
updateResultsHeader(result);
document.getElementById('exportBtn').disabled = false;
showStatus(`Correlation analysis complete! Found ${result.data_points} synchronized data points`, 'success');
} else {
throw new Error(result.error || 'Correlation calculation failed');
}
} catch (error) {
console.error('Error calculating correlations:', error);
showStatus('Failed to calculate correlations: ' + error.message, 'error');
} finally {
showLoading(false);
document.getElementById('analyzeBtn').disabled = false;
}
}
// Display correlation matrix
function displayCorrelationMatrix(matrix, significanceMatrix) {
const container = document.getElementById('matrixContainer');
if (!matrix || Object.keys(matrix).length === 0) {
container.innerHTML = '<div style="text-align: center; padding: 50px;">No correlation data to display</div>';
return;
}
const tags = Object.keys(matrix);
let html = '<table class="correlation-matrix"><thead><tr><th></th>';
tags.forEach(tag => {
html += `<th title="${tag}">${tag.length > 12 ? tag.substring(0, 12) + '...' : tag}</th>`;
});
html += '</tr></thead><tbody>';
tags.forEach(tagX => {
html += `<tr><th title="${tagX}">${tagX.length > 12 ? tagX.substring(0, 12) + '...' : tagX}</th>`;
tags.forEach(tagY => {
const correlation = matrix[tagX][tagY];
const significance = significanceMatrix ? significanceMatrix[tagX][tagY] : 1;
const isSignificant = significance < 0.05;
const cellClass = getCorrelationClass(correlation);
const displayValue = correlation.toFixed(3);
const significanceSymbol = isSignificant ? '*' : '';
html += `<td class="${cellClass}"
title="Correlation: ${displayValue}${significanceSymbol}\nP-value: ${significance.toFixed(4)}\n${tagX} vs ${tagY}"
onclick="showScatterPlot('${tagX}', '${tagY}')">
${displayValue}${significanceSymbol}
</td>`;
});
html += '</tr>';
});
html += '</tbody></table>';
container.innerHTML = html;
}
// Get CSS class for correlation strength
function getCorrelationClass(correlation) {
const abs = Math.abs(correlation);
if (abs >= 0.7) {
return correlation > 0 ? 'corr-strong-positive' : 'corr-strong-negative';
} else if (abs >= 0.3) {
return correlation > 0 ? 'corr-moderate-positive' : 'corr-moderate-negative';
} else {
return 'corr-neutral';
}
}
// Display insights
function displayInsights(insights, lagCorrelations) {
const container = document.getElementById('insightsContainer');
let html = '';
// Strong positive correlations
if (insights.strong_positive && Object.keys(insights.strong_positive).length > 0) {
html += `
<div class="insight-section">
<h4>🔥 Strong Positive Correlations</h4>
<div class="insight-list">
`;
Object.entries(insights.strong_positive).forEach(([pair, data]) => {
html += `
<div class="insight-item insight-positive">
<div class="insight-tags">${data.tags.join(' ↔ ')}</div>
<div class="insight-value">r = ${data.correlation.toFixed(3)} (p < ${data.p_value.toFixed(3)})</div>
<div class="insight-description">These variables move together strongly</div>
</div>
`;
});
html += '</div></div>';
}
// Strong negative correlations
if (insights.strong_negative && Object.keys(insights.strong_negative).length > 0) {
html += `
<div class="insight-section">
<h4>❄️ Strong Negative Correlations</h4>
<div class="insight-list">
`;
Object.entries(insights.strong_negative).forEach(([pair, data]) => {
html += `
<div class="insight-item insight-negative">
<div class="insight-tags">${data.tags.join(' ↔ ')}</div>
<div class="insight-value">r = ${data.correlation.toFixed(3)} (p < ${data.p_value.toFixed(3)})</div>
<div class="insight-description">These variables move in opposite directions</div>
</div>
`;
});
html += '</div></div>';
}
// Lag correlations
if (lagCorrelations && lagCorrelations.length > 0) {
html += `
<div class="insight-section">
<h4>⏰ Time-Delayed Relationships</h4>
<div class="insight-list">
`;
lagCorrelations.slice(0, 5).forEach(lag => {
if (Math.abs(lag.correlation) > 0.5) {
html += `
<div class="insight-item insight-neutral">
<div class="insight-tags">${lag.tag_x} → ${lag.tag_y}</div>
<div class="insight-value">r = ${lag.correlation.toFixed(3)} at ${lag.lag_minutes} min delay</div>
<div class="insight-description">Time-shifted correlation detected</div>
</div>
`;
}
});
html += '</div></div>';
}
// Summary statistics
if (insights.summary) {
const summary = insights.summary;
html += `
<div class="insight-section">
<h4>📊 Summary Statistics</h4>
<div class="insight-list">
<div class="insight-item insight-neutral">
<div class="insight-tags">Analysis Overview</div>
<div class="insight-description">
${summary.total_pairs} variable pairs analyzed<br>
Average correlation: ${summary.average_correlation.toFixed(3)}<br>
${summary.highly_correlated_pairs} strong correlations found
</div>
</div>
</div>
</div>
`;
}
if (html === '') {
html = '<div style="text-align: center; padding: 50px; color: rgba(255,255,255,0.6);">No significant insights found</div>';
}
container.innerHTML = html;
}
// Update results header
function updateResultsHeader(result) {
document.getElementById('resultsHeader').style.display = 'flex';
document.getElementById('dataPointsCount').textContent = result.data_points.toLocaleString();
document.getElementById('variablesCount').textContent = result.tags.length;
const strongCount = (Object.keys(result.insights.strong_positive || {}).length +
Object.keys(result.insights.strong_negative || {}).length);
document.getElementById('strongCorrelationsCount').textContent = strongCount;
}
// Show scatter plot (placeholder for future implementation)
function showScatterPlot(tagX, tagY) {
if (tagX === tagY) return;
showStatus(`Scatter plot for ${tagX} vs ${tagY} - Feature coming soon!`, 'info');
// Future implementation: Create modal with scatter plot
console.log(`Scatter plot requested: ${tagX} vs ${tagY}`<?php
// filepath: v:\controls\test\HMI\correlation_matrix.php
include "../../session.php";
include "../../userAccess.php";
// Database connection
$servername = "192.168.0.13";
$username = "opce";
$password = "opcelasuca";
$dbname = "archive";
try {
$pdo = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
if (isset($_POST['action'])) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Database connection failed: ' . $e->getMessage()]);
exit;
}
die("Connection failed: " . $e->getMessage());
}
// Handle AJAX requests
if (isset($_POST['action'])) {
while (ob_get_level()) {
ob_end_clean();
}
ob_start();
header('Content-Type: application/json');
try {
switch ($_POST['action']) {
case 'get_available_tags':
$stmt = $pdo->prepare("
SELECT DISTINCT n.name,
COUNT(*) as record_count,
MIN(h.TimeStamp) as earliest_date,
MAX(h.TimeStamp) as latest_date
FROM id_names n
INNER JOIN historicaldata h ON n.idnumber = h.ID
WHERE n.name IS NOT NULL
AND n.name != ''
AND h.Value IS NOT NULL
AND h.TimeStamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY n.name
HAVING record_count >= 100
ORDER BY record_count DESC, n.name ASC
");
$stmt->execute();
$tags = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'tags' => $tags,
'total_count' => count($tags)
]);
break;
case 'calculate_correlation_matrix':
$selectedTags = json_decode($_POST['tags'] ?? '[]', true);
$timeRange = (int)($_POST['time_range'] ?? 24);
$samplingInterval = (int)($_POST['sampling_interval'] ?? 10);
$correlationMethod = $_POST['correlation_method'] ?? 'pearson';
$lagAnalysis = (bool)($_POST['lag_analysis'] ?? false);
$maxLag = (int)($_POST['max_lag'] ?? 60);
if (empty($selectedTags) || !is_array($selectedTags) || count($selectedTags) < 2) {
throw new Exception('Please select at least 2 tags for correlation analysis');
}
if (count($selectedTags) > 20) {
throw new Exception('Maximum 20 tags allowed for performance reasons');
}
// Get synchronized data for all selected tags
$correlationData = getSynchronizedData($pdo, $selectedTags, $timeRange, $samplingInterval);
if (empty($correlationData)) {
throw new Exception('No synchronized data found for the selected tags and time period');
}
// Calculate correlation matrix
$correlationMatrix = calculateCorrelationMatrix($correlationData, $correlationMethod);
// Calculate lag correlations if requested
$lagCorrelations = [];
if ($lagAnalysis) {
$lagCorrelations = calculateLagCorrelations($correlationData, $maxLag, $correlationMethod);
}
// Calculate statistical significance
$significanceMatrix = calculateSignificanceMatrix($correlationData, $correlationMatrix);
// Generate insights
$insights = generateCorrelationInsights($correlationMatrix, $selectedTags, $significanceMatrix);
echo json_encode([
'success' => true,
'correlation_matrix' => $correlationMatrix,
'lag_correlations' => $lagCorrelations,
'significance_matrix' => $significanceMatrix,
'insights' => $insights,
'data_points' => count($correlationData),
'tags' => $selectedTags,
'time_range' => $timeRange,
'method' => $correlationMethod
]);
break;
case 'get_scatter_plot_data':
$tagX = $_POST['tag_x'] ?? '';
$tagY = $_POST['tag_y'] ?? '';
$timeRange = (int)($_POST['time_range'] ?? 24);
$maxPoints = (int)($_POST['max_points'] ?? 1000);
if (empty($tagX) || empty($tagY)) {
throw new Exception('Both X and Y tags must be specified');
}
$scatterData = getScatterPlotData($pdo, $tagX, $tagY, $timeRange, $maxPoints);
echo json_encode([
'success' => true,
'scatter_data' => $scatterData,
'tag_x' => $tagX,
'tag_y' => $tagY
]);
break;
case 'export_correlation_data':
$selectedTags = json_decode($_POST['tags'] ?? '[]', true);
$timeRange = (int)($_POST['time_range'] ?? 24);
$format = $_POST['format'] ?? 'csv';
if (empty($selectedTags)) {
throw new Exception('No tags selected for export');
}
$exportData = getSynchronizedData($pdo, $selectedTags, $timeRange, 5); // 5-minute intervals for export
if ($format === 'csv') {
$csv = exportToCSV($exportData, $selectedTags);
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="correlation_data_' . date('Y-m-d_H-i-s') . '.csv"');
echo $csv;
exit;
}
echo json_encode([
'success' => true,
'message' => 'Export format not supported'
]);
break;
default:
throw new Exception('Invalid action specified');
}
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
} catch (Error $e) {
echo json_encode(['success' => false, 'error' => 'PHP Error: ' . $e->getMessage()]);
}
ob_end_flush();
exit;
}
// Helper function to get synchronized data for correlation analysis
function getSynchronizedData($pdo, $tags, $timeRange, $samplingInterval) {
$placeholders = str_repeat('?,', count($tags) - 1) . '?';
// Create time buckets for synchronization
$sql = "
SELECT
FLOOR(UNIX_TIMESTAMP(h.TimeStamp) / (? * 60)) * (? * 60) as time_bucket,
n.name as tag_name,
AVG(h.Value) as avg_value
FROM historicaldata h
INNER JOIN id_names n ON h.ID = n.idnumber
WHERE n.name IN ($placeholders)
AND h.TimeStamp >= DATE_SUB(NOW(), INTERVAL ? HOUR)
AND h.Value IS NOT NULL
GROUP BY time_bucket, n.name
HAVING COUNT(*) >= 1
ORDER BY time_bucket ASC
";
$params = array_merge([$samplingInterval, $samplingInterval], $tags, [$timeRange]);
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rawData = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Organize data by time bucket
$bucketData = [];
foreach ($rawData as $row) {
$bucket = $row['time_bucket'];
$tag = $row['tag_name'];
$value = (float)$row['avg_value'];
if (!isset($bucketData[$bucket])) {
$bucketData[$bucket] = [];
}
$bucketData[$bucket][$tag] = $value;
}
// Filter buckets that have data for all tags
$synchronizedData = [];
foreach ($bucketData as $bucket => $data) {
if (count($data) === count($tags)) {
$row = ['timestamp' => $bucket];
foreach ($tags as $tag) {
$row[$tag] = $data[$tag];
}
$synchronizedData[] = $row;
}
}
return $synchronizedData;
}
// Calculate correlation matrix
function calculateCorrelationMatrix($data, $method = 'pearson') {
if (empty($data)) return [];
$tags = array_keys($data[0]);
$tags = array_filter($tags, function($tag) { return $tag !== 'timestamp'; });
$matrix = [];
foreach ($tags as $tagX) {
$matrix[$tagX] = [];
foreach ($tags as $tagY) {
if ($tagX === $tagY) {
$matrix[$tagX][$tagY] = 1.0;
} else {
$valuesX = array_column($data, $tagX);
$valuesY = array_column($data, $tagY);
switch ($method) {
case 'spearman':
$correlation = calculateSpearmanCorrelation($valuesX, $valuesY);
break;
case 'kendall':
$correlation = calculateKendallCorrelation($valuesX, $valuesY);
break;
default: // pearson
$correlation = calculatePearsonCorrelation($valuesX, $valuesY);
break;
}
$matrix[$tagX][$tagY] = $correlation;
}
}
}
return $matrix;
}
// Calculate Pearson correlation coefficient
function calculatePearsonCorrelation($x, $y) {
$n = count($x);
if ($n < 2) return 0;
$sumX = array_sum($x);
$sumY = array_sum($y);
$sumXY = 0;
$sumX2 = 0;
$sumY2 = 0;
for ($i = 0; $i < $n; $i++) {
$sumXY += $x[$i] * $y[$i];
$sumX2 += $x[$i] * $x[$i];
$sumY2 += $y[$i] * $y[$i];
}
$numerator = $n * $sumXY - $sumX * $sumY;
$denominator = sqrt(($n * $sumX2 - $sumX * $sumX) * ($n * $sumY2 - $sumY * $sumY));
if ($denominator == 0) return 0;
return $numerator / $denominator;
}
// Calculate Spearman rank correlation
function calculateSpearmanCorrelation($x, $y) {
$rankX = array_values(calculateRanks($x));
$rankY = array_values(calculateRanks($y));
return calculatePearsonCorrelation($rankX, $rankY);
}
// Calculate ranks for Spearman correlation
function calculateRanks($values) {
$indexed = [];
foreach ($values as $index => $value) {
$indexed[] = ['value' => $value, 'index' => $index];
}
usort($indexed, function($a, $b) {
return $a['value'] <=> $b['value'];
});
$ranks = [];
for ($i = 0; $i < count($indexed); $i++) {
$ranks[$indexed[$i]['index']] = $i + 1;
}
return $ranks;
}
// Calculate Kendall's tau correlation (simplified version)
function calculateKendallCorrelation($x, $y) {
$n = count($x);
if ($n < 2) return 0;
$concordant = 0;
$discordant = 0;
for ($i = 0; $i < $n - 1; $i++) {
for ($j = $i + 1; $j < $n; $j++) {
$signX = ($x[$j] - $x[$i]) <=> 0;
$signY = ($y[$j] - $y[$i]) <=> 0;
if ($signX * $signY > 0) {
$concordant++;
} elseif ($signX * $signY < 0) {
$discordant++;
}
}
}
$totalPairs = $n * ($n - 1) / 2;
if ($totalPairs == 0) return 0;
return ($concordant - $discordant) / $totalPairs;
}
// Calculate lag correlations
function calculateLagCorrelations($data, $maxLag, $method = 'pearson') {
if (empty($data) || $maxLag <= 0) return [];
$tags = array_keys($data[0]);
$tags = array_filter($tags, function($tag) { return $tag !== 'timestamp'; });
$lagCorrelations = [];
foreach ($tags as $tagX) {
foreach ($tags as $tagY) {
if ($tagX === $tagY) continue;
$valuesX = array_column($data, $tagX);
$valuesY = array_column($data, $tagY);
$bestCorrelation = 0;
$bestLag = 0;
for ($lag = 0; $lag <= min($maxLag, count($valuesX) - 10); $lag++) {
$laggedX = array_slice($valuesX, $lag);
$laggedY = array_slice($valuesY, 0, count($laggedX));
if (count($laggedX) < 10) break;
$correlation = calculatePearsonCorrelation($laggedX, $laggedY);
if (abs($correlation) > abs($bestCorrelation)) {
$bestCorrelation = $correlation;
$bestLag = $lag;
}
}
$lagCorrelations[] = [
'tag_x' => $tagX,
'tag_y' => $tagY,
'correlation' => $bestCorrelation,
'lag' => $bestLag,
'lag_minutes' => $bestLag * 10 // Assuming 10-minute intervals
];
}
}
// Sort by absolute correlation strength
usort($lagCorrelations, function($a, $b) {
return abs($b['correlation']) <=> abs($a['correlation']);
});
return array_slice($lagCorrelations, 0, 20); // Return top 20
}
// Calculate statistical significance
function calculateSignificanceMatrix($data, $correlationMatrix) {
$n = count($data);
$significanceMatrix = [];
foreach ($correlationMatrix as $tagX => $row) {
$significanceMatrix[$tagX] = [];
foreach ($row as $tagY => $correlation) {
if ($tagX === $tagY) {
$significanceMatrix[$tagX][$tagY] = 0; // Perfect correlation, no p-value
} else {
// Calculate t-statistic and p-value
if (abs($correlation) >= 1.0) {
$pValue = 0;
} else {
$tStat = $correlation * sqrt(($n - 2) / (1 - $correlation * $correlation));
$pValue = 2 * (1 - getTDistributionCDF(abs($tStat), $n - 2));
}
$significanceMatrix[$tagX][$tagY] = $pValue;
}
}
}
return $significanceMatrix;
}
// Simplified t-distribution CDF approximation
function getTDistributionCDF($t, $df) {
if ($df <= 0) return 0.5;
// Simple approximation for large degrees of freedom
if ($df >= 30) {
return 0.5 * (1 + erf($t / sqrt(2)));
}
// Very simplified approximation for smaller df
$x = $df / ($df + $t * $t);
return 1 - 0.5 * pow($x, $df / 2);
}
// Error function approximation
function erf($x) {
$sign = ($x >= 0) ? 1 : -1;
$x = abs($x);
$a1 = 0.254829592;
$a2 = -0.284496736;
$a3 = 1.421413741;
$a4 = -1.453152027;
$a5 = 1.061405429;
$p = 0.3275911;
$t = 1.0 / (1.0 + $p * $x);
$y = 1.0 - (((($a5 * $t + $a4) * $t) + $a3) * $t + $a2) * $t + $a1) * $t * exp(-$x * $x);
return $sign * $y;
}
// Generate correlation insights
function generateCorrelationInsights($correlationMatrix, $tags, $significanceMatrix) {
$insights = [];
// Find strongest positive correlations
$strongPositive = [];
$strongNegative = [];
$weakCorrelations = [];
foreach ($correlationMatrix as $tagX => $row) {
foreach ($row as $tagY => $correlation) {
if ($tagX === $tagY) continue;
$pValue = $significanceMatrix[$tagX][$tagY];
$significant = $pValue < 0.05;
$pair = [$tagX, $tagY];
sort($pair); // Avoid duplicates
$pairKey = implode(' - ', $pair);
if ($correlation > 0.7 && $significant) {
$strongPositive[$pairKey] = [
'correlation' => $correlation,
'p_value' => $pValue,
'tags' => $pair
];
} elseif ($correlation < -0.7 && $significant) {
$strongNegative[$pairKey] = [
'correlation' => $correlation,
'p_value' => $pValue,
'tags' => $pair
];
} elseif (abs($correlation) < 0.1) {
$weakCorrelations[$pairKey] = [
'correlation' => $correlation,
'p_value' => $pValue,
'tags' => $pair
];
}
}
}
// Sort by correlation strength
uasort($strongPositive, function($a, $b) { return $b['correlation'] <=> $a['correlation']; });
uasort($strongNegative, function($a, $b) { return $a['correlation'] <=> $b['correlation']; });
$insights['strong_positive'] = array_slice($strongPositive, 0, 5, true);
$insights['strong_negative'] = array_slice($strongNegative, 0, 5, true);
$insights['weak_correlations'] = array_slice($weakCorrelations, 0, 5, true);
// Summary statistics
$allCorrelations = [];
foreach ($correlationMatrix as $tagX => $row) {
foreach ($row as $tagY => $correlation) {
if ($tagX !== $tagY) {
$allCorrelations[] = abs($correlation);
}
}
}
$insights['summary'] = [
'total_pairs' => count($allCorrelations) / 2, // Divide by 2 to avoid counting each pair twice
'average_correlation' => array_sum($allCorrelations) / count($allCorrelations),
'max_correlation' => max($allCorrelations),
'highly_correlated_pairs' => count($strongPositive) + count($strongNegative)
];
return $insights;
}
// Get scatter plot data for two specific tags
function getScatterPlotData($pdo, $tagX, $tagY, $timeRange, $maxPoints) {
$stmt = $pdo->prepare("
SELECT
hx.Value as x_value,
hy.Value as y_value,
hx.TimeStamp
FROM historicaldata hx
INNER JOIN id_names nx ON hx.ID = nx.idnumber
INNER JOIN historicaldata hy ON hx.TimeStamp = hy.TimeStamp
INNER JOIN id_names ny ON hy.ID = ny.idnumber
WHERE nx.name = ?
AND ny.name = ?
AND hx.TimeStamp >= DATE_SUB(NOW(), INTERVAL ? HOUR)
AND hx.Value IS NOT NULL
AND hy.Value IS NOT NULL
ORDER BY hx.TimeStamp DESC
LIMIT ?
");
$stmt->execute([$tagX, $tagY, $timeRange, $maxPoints]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Export correlation data to CSV
function exportToCSV($data, $tags) {
if (empty($data)) return '';
$csv = 'Timestamp,' . implode(',', $tags) . "\n";
foreach ($data as $row) {
$csvRow = [date('Y-m-d H:i:s', $row['timestamp'])];
foreach ($tags as $tag) {
$csvRow[] = $row[$tag] ?? '';
}
$csv .= implode(',', $csvRow) . "\n";
}
return $csv;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LASUCA Controls - Correlation Matrix Analysis</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3@7.8.5/dist/d3.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: #fff;
min-height: 100vh;
}
.dashboard-header {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(15px);
padding: 25px;
text-align: center;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.dashboard-header h1 {
color: #fff;
font-size: 2.8rem;
margin: 0;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.dashboard-header .subtitle {
color: #ffd700;
font-size: 1.3rem;
margin-top: 10px;
font-weight: 400;
}
.container {
max-width: 1900px;
margin: 0 auto;
padding: 25px;
}
.analysis-layout {
display: grid;
grid-template-columns: 350px 1fr;
gap: 25px;
height: calc(100vh - 140px);
}
/* Control Panel */
.control-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 25px;
border: 1px solid rgba(255, 255, 255, 0.2);
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.control-section {
margin-bottom: 30px;
}
.control-section h3 {
color: #ffd700;
font-size: 1.2rem;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 2px solid rgba(255, 215, 0, 0.3);
padding-bottom: 8px;
}
.tag-selection {
max-height: 300px;
overflow-y: auto;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
}
.tag-item {
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.tag-item:hover {
background: rgba(255, 255, 255, 0.1);
border-radius: 6px;
}
.tag-item:last-child {
border-bottom: none;
}
.tag-item input[type="checkbox"] {
width: 18px;
height: 18px;
margin-right: 12px;
accent-color: #ffd700;
}
.tag-item label {
cursor: pointer;
flex: 1;
font-size: 0.9rem;
display: flex;
flex-direction: column;
}
.tag-name {
color: #fff;
font-weight: 600;
}
.tag-stats {
color: rgba(255, 255, 255, 0.7);
font-size: 0.75rem;
margin-top: 2px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #fff;
font-size: 0.95rem;
}
.form-group select,
.form-group input {
width: 100%;
padding: 12px;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
color: #fff;
font-size: 0.9rem;
transition: all 0.3s ease;
}
.form-group select:focus,
.form-group input:focus {
outline: none;
border-color: #ffd700;
box-shadow: 0 0 0 3px rgba(255, 215, 0, 0.2);
}
.form-group select option {
background: #2a5298;
color: #fff;
}
/* Action Buttons */
.action-buttons {
display: flex;
flex-direction: column;
gap: 12px;
}
button {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
color: white;
border: none;
padding: 14px 20px;
border-radius: 12px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
button:disabled {
background: rgba(255, 255, 255, 0.2);
cursor: not-allowed;
transform: none;
}
button.analyze-btn {
background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
}
button.export-btn {
background: linear-gradient(135deg, #0984e3 0%, #74b9ff 100%);
}
button.clear-btn {
background: linear-gradient(135deg, #e17055 0%, #d63031 100%);
}
/* Main Analysis Area */
.analysis-content {
display: grid;
grid-template-rows: auto 1fr;
gap: 20px;
}
/* Results Header */
.results-header {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 15px;
padding: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex;
justify-content: space-between;
align-items: center;
}
.results-title {
color: #ffd700;
font-size: 1.5rem;
font-weight: 700;
}
.results-stats {
display: flex;
gap: 20px;
font-size: 0.9rem;
}
.stat-item {
text-align: center;
}
.stat-value {
display: block;
font-size: 1.4rem;
font-weight: 700;
color: #4ecdc4;
}
.stat-label {
color: rgba(255, 255, 255, 0.8);
font-size: 0.8rem;
}
/* Analysis Tabs */
.analysis-tabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
height: 100%;
}
.tab-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border-radius: 20px;
padding: 25px;
border: 1px solid rgba(255, 255, 255, 0.2);
overflow: hidden;
display: flex;
flex-direction: column;
}
.tab-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
padding-bottom: 15px;
}
.tab-title {
color: #ffd700;
font-size: 1.3rem;
font-weight: 600;
}
/* Correlation Matrix */
.matrix-container {
flex: 1;
overflow: auto;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
}
.correlation-matrix {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.correlation-matrix th {
background: rgba(255, 255, 255, 0.2);
color: #fff;
padding: 12px 8px;
text-align: center;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.correlation-matrix td {
padding: 8px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.1);
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.correlation-matrix td:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.05);
}
/* Correlation color coding */
.corr-strong-positive { background: rgba(46, 204, 113, 0.8); color: #fff; }
.corr-moderate-positive { background: rgba(52, 152, 219, 0.6); color: #fff; }
.corr-weak-positive { background: rgba(52, 152, 219, 0.3); color: #fff; }
.corr-neutral { background: rgba(255, 255, 255, 0.1); color: #fff; }
.corr-weak-negative { background: rgba(231, 76, 60, 0.3); color: #fff; }
.corr-moderate-negative { background: rgba(231, 76, 60, 0.6); color: #fff; }
.corr-strong-negative { background: rgba(231, 76, 60, 0.8); color: #fff; }
/* Insights Panel */
.insights-container {
flex: 1;
overflow-y: auto;
}
.insight-section {
margin-bottom: 25px;
}
.insight-section h4 {
color: #4ecdc4;
font-size: 1.1rem;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.insight-list {
display: grid;
gap: 10px;
}
.insight-item {
background: rgba(255, 255, 255, 0.1);
padding: 12px;
border-radius: 10px;
border-left: 4px solid;
transition: all 0.3s ease;
}
.insight-item:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateX(5px);
}
.insight-positive { border-left-color: #00b894; }
.insight-negative { border-left-color: #e17055; }
.insight-neutral { border-left-color: #74b9ff; }
.insight-tags {
font-weight: 600;
font-size: 0.95rem;
}
.insight-value {
color: #ffd700;
font-weight: 700;
}
.insight-description {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.8);
margin-top: 4px;
}
/* Scatter Plot Container */
.scatter-container {
flex: 1;
position: relative;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
overflow: hidden;
}
.scatter-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.scatter-controls select {
flex: 1;
padding: 8px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 6px;
background: rgba(255, 255, 255, 0.1);
color: #fff;
font-size: 0.85rem;
}
/* Loading and Status */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(5px);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-spinner {
width: 80px;
height: 80px;
border: 6px solid rgba(255, 255, 255, 0.3);
border-top: 6px solid #ffd700;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.status-message {
padding: 15px 20px;
margin-bottom: 20px;
border-radius: 12px;
font-weight: 600;
text-align: center;
}
.status-message.success {
background: rgba(0, 184, 148, 0.2);
border: 2px solid rgba(0, 184, 148, 0.5);
color: #00b894;
}
.status-message.error {
background: rgba(225, 112, 85, 0.2);
border: 2px solid rgba(225, 112, 85, 0.5);
color: #e17055;
}
.status-message.info {
background: rgba(116, 185, 255, 0.2);
border: 2px solid rgba(116, 185, 255, 0.5);
color: #74b9ff;
}
/* Legend */
.correlation-legend {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 15px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.8rem;
}
.legend-color {
width: 20px;
height: 12px;
border-radius: 3px;
}
/* Responsive Design */
@media (max-width: 1400px) {
.analysis-layout {
grid-template-columns: 300px 1fr;
}
.analysis-tabs {
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
}
@media (max-width: 1000px) {
.analysis-layout {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
}
.control-panel {
max-height: 400px;
}
}
/* Custom Scrollbars */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 215, 0, 0.5);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 215, 0, 0.7);
}
</style>
</head>
<body>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner"></div>
</div>
<!-- Header -->
<div class="dashboard-header">
<h1>🔗 LASUCA Controls - Multi-Variable Correlation Matrix</h1>
<p class="subtitle">Discover Hidden Relationships Between Process Variables</p>
</div>
<div class="container">
<!-- Status Messages -->
<div id="statusMessages"></div>
<div class="analysis-layout">
<!-- Control Panel -->
<div class="control-panel">
<!-- Tag Selection -->
<div class="control-section">
<h3>🏷️ Select Variables</h3>
<div class="tag-selection" id="tagSelection">
<div style="text-align: center; padding: 20px; color: rgba(255,255,255,0.6);">
Loading available tags...
</div>
</div>
<div class="action-buttons" style="margin-top: 15px;">
<button onclick="selectAllTags()">Select All</button>
<button onclick="clearAllTags()" class="clear-btn">Clear All</button>
</div>
</div>
<!-- Analysis Parameters -->
<div class="control-section">
<h3>⚙️ Analysis Settings</h3>
<div class="form-group">
<label for="timeRange">Time Range:</label>
<select id="timeRange">
<option value="1">Last Hour</option>
<option value="6">Last 6 Hours</option>
<option value="24" selected>Last 24 Hours</option>
<option value="72">Last 3 Days</option>
<option value="168">Last Week</option>
<option value="720">Last Month</option>
</select>
</div>
<div class="form-group">
<label for="samplingInterval">Sampling Interval:</label>
<select id="samplingInterval">
<option value="1">1 Minute</option>
<option value="5">5 Minutes</option>
<option value="10" selected>10 Minutes</option>
<option value="30">30 Minutes</option>
<option value="60">1 Hour</option>
</select>
</div>
<div class="form-group">
<label for="correlationMethod">Correlation Method:</label>
<select id="correlationMethod">
<option value="pearson" selected>Pearson (Linear)</option>
<option value="spearman">Spearman (Rank)</option>
<option value="kendall">Kendall's Tau</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="lagAnalysis" style="width: auto; margin-right: 8px;">
Enable Lag Analysis
</label>
</div>
<div class="form-group" id="maxLagGroup" style="display: none;">
<label for="maxLag">Maximum Lag (intervals):</label>
<input type="number" id="maxLag" value="6" min="1" max="100">
</div>
</div>
<!-- Action Buttons -->
<div class="control-section">
<h3>🚀 Actions</h3>
<div class="action-buttons">
<button onclick="calculateCorrelations()" class="analyze-btn" id="analyzeBtn">
📊 Calculate Correlations
</button>
<button onclick="exportResults()" class="export-btn" id="exportBtn" disabled>
📁 Export Data
</button>
<button onclick="clearResults()" class="clear-btn">
🗑️ Clear Results
</button>
</div>
</div>
</div>
<!-- Analysis Content -->
<div class="analysis-content">
<!-- Results Header -->
<div class="results-header" id="resultsHeader" style="display: none;">
<div class="results-title">Correlation Analysis Results</div>
<div class="results-stats">
<div class="stat-item">
<span class="stat-value" id="dataPointsCount">0</span>
<span class="stat-label">Data Points</span>
</div>
<div class="stat-item">
<span class="stat-value" id="variablesCount">0</span>
<span class="stat-label">Variables</span>
</div>
<div class="stat-item">
<span class="stat-value" id="strongCorrelationsCount">0</span>
<span class="stat-label">Strong Correlations</span>
</div>
</div>
</div>
<!-- Analysis Tabs -->
<div class="analysis-tabs">
<!-- Correlation Matrix Tab -->
<div class="tab-panel">
<div class="tab-header">
<div class="tab-title">🎯 Correlation Matrix</div>
</div>
<div class="matrix-container" id="matrixContainer">
<div style="text-align: center; padding: 50px; color: rgba(255,255,255,0.6);">
Select variables and click "Calculate Correlations" to see the matrix
</div>
</div>
<div class="correlation-legend">
<div class="legend-item">
<div class="legend-color corr-strong-positive"></div>
<span>Strong Positive (>0.7)</span>
</div>
<div class="legend-item">
<div class="legend-color corr-moderate-positive"></div>
<span>Moderate Positive (0.3-0.7)</span>
</div>
<div class="legend-item">
<div class="legend-color corr-neutral"></div>
<span>Weak (-0.3 to 0.3)</span>
</div>
<div class="legend-item">
<div class="legend-color corr-moderate-negative"></div>
<span>Moderate Negative (-0.7 to -0.3)</span>
</div>
<div class="legend-item">
<div class="legend-color corr-strong-negative"></div>
<span>Strong Negative (<-0.7)</span>
</div>
</div>
</div>
<!-- Insights Tab -->
<div class="tab-panel">
<div class="tab-header">
<div class="tab-title">💡 Key Insights</div>
</div>
<div class="insights-container" id="insightsContainer">