2892 lines
98 KiB
PHP
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">
|
|
…
|