Folder reorganize 1
This commit is contained in:
651
trends/live/fivechart.php
Normal file
651
trends/live/fivechart.php
Normal file
@@ -0,0 +1,651 @@
|
||||
<?php // phpcs:ignoreFile
|
||||
|
||||
require __DIR__ . '/../../session.php';
|
||||
require __DIR__ . '/../../userAccess.php';
|
||||
|
||||
$pageTitle = 'Multi-Tag Trend';
|
||||
$pageSubtitle = 'Monitor up to five tags over three hours';
|
||||
$pageDescription = 'Launch the multi-tag trend to track up to five historian tags simultaneously.';
|
||||
$assetBasePath = '../../';
|
||||
$layoutWithoutSidebar = true;
|
||||
$layoutReturnUrl = '../../overview.php';
|
||||
$layoutReturnLabel = 'Back to overview';
|
||||
|
||||
require __DIR__ . '/../../includes/layout/header.php';
|
||||
?>
|
||||
|
||||
<div class="app-content">
|
||||
<section class="data-panel trend-panel">
|
||||
<header class="trend-panel__header">
|
||||
<div>
|
||||
<h2 id="chartTitle">Multi-tag real-time trend</h2>
|
||||
<p>Select up to five tags to stream a three-hour sliding history.</p>
|
||||
</div>
|
||||
<span class="trend-status trend-status--stopped" id="chartStatus">STOPPED</span>
|
||||
</header>
|
||||
|
||||
<div class="trend-control-grid">
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect1">Tag 1</label>
|
||||
<select id="tagSelect1" class="tag-select">
|
||||
<option value="">Loading tags...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect2">Tag 2</label>
|
||||
<select id="tagSelect2" class="tag-select">
|
||||
<option value="">Loading tags...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect3">Tag 3</label>
|
||||
<select id="tagSelect3" class="tag-select">
|
||||
<option value="">Loading tags...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect4">Tag 4</label>
|
||||
<select id="tagSelect4" class="tag-select">
|
||||
<option value="">Loading tags...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect5">Tag 5</label>
|
||||
<select id="tagSelect5" class="tag-select">
|
||||
<option value="">Loading tags...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="updateInterval">Update interval</label>
|
||||
<select id="updateInterval">
|
||||
<option value="10700" selected>Every 10 seconds</option>
|
||||
<option value="30000">Every 30 seconds</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label>Time window</label>
|
||||
<div class="time-window-display">Last 3 hours (sliding)</div>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="startBtn">Controls</label>
|
||||
<div class="trend-control__actions">
|
||||
<button type="button" class="button button--success" id="startBtn" onclick="startRealTime()">Start</button>
|
||||
<button type="button" class="button button--danger" id="stopBtn" onclick="stopRealTime()" disabled>Stop</button>
|
||||
<button type="button" class="button button--ghost" onclick="clearChart()">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="status" class="trend-connection trend-connection--disconnected">
|
||||
Disconnected — Select at least one tag and click Start to begin trending
|
||||
</div>
|
||||
|
||||
<div class="trend-stat-grid">
|
||||
<div class="trend-stat trend-stat--series1">
|
||||
<span id="currentValue1" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="currentLabel1">Tag 1 current</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series1">
|
||||
<span id="minValue1" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="minLabel1">Tag 1 min</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series1">
|
||||
<span id="maxValue1" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="maxLabel1">Tag 1 max</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series2">
|
||||
<span id="currentValue2" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="currentLabel2">Tag 2 current</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series2">
|
||||
<span id="minValue2" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="minLabel2">Tag 2 min</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series2">
|
||||
<span id="maxValue2" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="maxLabel2">Tag 2 max</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series3">
|
||||
<span id="currentValue3" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="currentLabel3">Tag 3 current</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series3">
|
||||
<span id="minValue3" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="minLabel3">Tag 3 min</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series3">
|
||||
<span id="maxValue3" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="maxLabel3">Tag 3 max</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series4">
|
||||
<span id="currentValue4" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="currentLabel4">Tag 4 current</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series4">
|
||||
<span id="minValue4" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="minLabel4">Tag 4 min</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series4">
|
||||
<span id="maxValue4" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="maxLabel4">Tag 4 max</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series5">
|
||||
<span id="currentValue5" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="currentLabel5">Tag 5 current</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series5">
|
||||
<span id="minValue5" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="minLabel5">Tag 5 min</span>
|
||||
</div>
|
||||
<div class="trend-stat trend-stat--series5">
|
||||
<span id="maxValue5" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label" id="maxLabel5">Tag 5 max</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="dataPoints" class="trend-stat__value">0</span>
|
||||
<span class="trend-stat__label">Total points</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="lastUpdate" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Last update</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="trend-chart">
|
||||
<canvas id="realtimeChart"></canvas>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.1/dist/chartjs-adapter-moment.min.js"></script>
|
||||
|
||||
<script>
|
||||
const MAX_TAGS = 5;
|
||||
const timeWindowMinutes = 180;
|
||||
const defaultLabels = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5'];
|
||||
const DATASET_STYLES = [
|
||||
{ border: '#3498db', background: 'rgba(52, 152, 219, 0.15)' },
|
||||
{ border: '#27ae60', background: 'rgba(39, 174, 96, 0.15)' },
|
||||
{ border: '#f39c12', background: 'rgba(243, 156, 18, 0.15)' },
|
||||
{ border: '#e74c3c', background: 'rgba(231, 76, 60, 0.15)' },
|
||||
{ border: '#9b59b6', background: 'rgba(155, 89, 182, 0.15)' }
|
||||
];
|
||||
|
||||
let chart = null;
|
||||
let updateTimer = null;
|
||||
let isRunning = false;
|
||||
let chartData = Array.from({ length: MAX_TAGS }, () => []);
|
||||
|
||||
function getTagSelectElements() {
|
||||
return Array.from(document.querySelectorAll('.tag-select'));
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
const ctx = document.getElementById('realtimeChart').getContext('2d');
|
||||
const datasets = DATASET_STYLES.map((style, index) => ({
|
||||
label: defaultLabels[index],
|
||||
data: [],
|
||||
borderColor: style.border,
|
||||
backgroundColor: style.background,
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 0,
|
||||
hitRadius: 0,
|
||||
hidden: index !== 0
|
||||
}));
|
||||
|
||||
chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: { datasets },
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: { duration: 300 },
|
||||
interaction: { intersect: false, mode: 'index' },
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
displayFormats: {
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'HH:mm'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Time (Last 3 Hours)',
|
||||
font: { size: 12, weight: 'bold' }
|
||||
},
|
||||
grid: { color: 'rgba(0, 0, 0, 0.1)' },
|
||||
min: moment().subtract(timeWindowMinutes, 'minutes').toDate(),
|
||||
max: moment().toDate()
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Tag value',
|
||||
color: '#2c3e50',
|
||||
font: { size: 12, weight: 'bold' }
|
||||
},
|
||||
grid: { drawOnChartArea: true, color: 'rgba(0, 0, 0, 0.08)' },
|
||||
ticks: { color: '#2c3e50', font: { weight: 'bold' } }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top',
|
||||
labels: {
|
||||
font: { size: 12, weight: 'bold' },
|
||||
usePointStyle: true,
|
||||
padding: 18
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(44, 62, 80, 0.9)',
|
||||
titleColor: '#ecf0f1',
|
||||
bodyColor: '#ecf0f1',
|
||||
borderColor: '#2c3e50',
|
||||
borderWidth: 1,
|
||||
titleFont: { size: 13, weight: 'bold' },
|
||||
bodyFont: { size: 12 },
|
||||
callbacks: {
|
||||
title(context) {
|
||||
return moment(context[0].parsed.x).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
afterTitle(context) {
|
||||
const now = moment();
|
||||
const pointTime = moment(context[0].parsed.x);
|
||||
const minutesAgo = now.diff(pointTime, 'minutes');
|
||||
if (minutesAgo <= 1) {
|
||||
const secondsAgo = now.diff(pointTime, 'seconds');
|
||||
return `(${secondsAgo} seconds ago)`;
|
||||
}
|
||||
return `(${minutesAgo} minutes ago)`;
|
||||
},
|
||||
label(context) {
|
||||
const datasetLabel = context.dataset.label;
|
||||
const rawValue = context.parsed.y;
|
||||
if (rawValue === null || Number.isNaN(rawValue)) {
|
||||
return `${datasetLabel}: --`;
|
||||
}
|
||||
const value = Number(rawValue).toFixed(2);
|
||||
return `${datasetLabel}: ${value}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadTags() {
|
||||
const selects = getTagSelectElements();
|
||||
if (selects.length === 0) {
|
||||
console.error('Tag select elements not found in DOM.');
|
||||
return;
|
||||
}
|
||||
|
||||
const placeholders = [
|
||||
'Select primary tag...',
|
||||
'Optional tag 2...',
|
||||
'Optional tag 3...',
|
||||
'Optional tag 4...',
|
||||
'Optional tag 5...'
|
||||
];
|
||||
|
||||
const setLoadingMessage = (message) => {
|
||||
selects.forEach((select) => {
|
||||
select.innerHTML = `<option value="">${message}</option>`;
|
||||
});
|
||||
};
|
||||
|
||||
setLoadingMessage('Loading tags...');
|
||||
|
||||
try {
|
||||
const response = await fetch('get_tags.php', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
cache: 'no-cache'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
if (!payload.success || !Array.isArray(payload.tags) || payload.tags.length === 0) {
|
||||
const errorText = payload.error || 'No tags available';
|
||||
setLoadingMessage(errorText);
|
||||
return;
|
||||
}
|
||||
|
||||
selects.forEach((select, index) => {
|
||||
select.innerHTML = `<option value="">${placeholders[index] || 'Optional tag...'}</option>`;
|
||||
payload.tags.forEach((tag) => {
|
||||
if (!tag || !tag.name) {
|
||||
return;
|
||||
}
|
||||
const option = document.createElement('option');
|
||||
option.value = tag.name;
|
||||
option.textContent = tag.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load tags:', error);
|
||||
setLoadingMessage(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getActiveTags() {
|
||||
return getTagSelectElements()
|
||||
.map((select, index) => ({ index, tagName: select.value.trim() }))
|
||||
.filter((item) => item.tagName !== '');
|
||||
}
|
||||
|
||||
function setControlsDisabled(disabled) {
|
||||
getTagSelectElements().forEach((select) => {
|
||||
select.disabled = disabled;
|
||||
});
|
||||
}
|
||||
|
||||
function updateChartTitle() {
|
||||
const activeTags = getActiveTags();
|
||||
if (activeTags.length === 0) {
|
||||
document.getElementById('chartTitle').textContent = 'Multi-tag real-time trend';
|
||||
return;
|
||||
}
|
||||
|
||||
const title = activeTags.map((item) => item.tagName).join(', ');
|
||||
document.getElementById('chartTitle').textContent = title;
|
||||
}
|
||||
|
||||
function updateStatus(state, message) {
|
||||
const statusElement = document.getElementById('status');
|
||||
const chartStatus = document.getElementById('chartStatus');
|
||||
const classSuffix = state === 'connected' ? 'connected' : 'disconnected';
|
||||
|
||||
statusElement.className = `trend-connection trend-connection--${classSuffix}`;
|
||||
statusElement.textContent = message;
|
||||
|
||||
if (state === 'connected') {
|
||||
chartStatus.className = 'trend-status trend-status--running';
|
||||
chartStatus.textContent = 'RUNNING';
|
||||
} else {
|
||||
chartStatus.className = 'trend-status trend-status--stopped';
|
||||
chartStatus.textContent = 'STOPPED';
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatistics() {
|
||||
const selects = getTagSelectElements();
|
||||
let totalPoints = 0;
|
||||
let hasData = false;
|
||||
|
||||
selects.forEach((select, index) => {
|
||||
const labelBase = select.value ? `${select.value}` : defaultLabels[index];
|
||||
const currentLabel = document.getElementById(`currentLabel${index + 1}`);
|
||||
const minLabel = document.getElementById(`minLabel${index + 1}`);
|
||||
const maxLabel = document.getElementById(`maxLabel${index + 1}`);
|
||||
|
||||
if (currentLabel) {
|
||||
currentLabel.textContent = `${labelBase} current`;
|
||||
}
|
||||
if (minLabel) {
|
||||
minLabel.textContent = `${labelBase} min`;
|
||||
}
|
||||
if (maxLabel) {
|
||||
maxLabel.textContent = `${labelBase} max`;
|
||||
}
|
||||
|
||||
const points = chartData[index] || [];
|
||||
totalPoints += points.length;
|
||||
|
||||
const currentValue = document.getElementById(`currentValue${index + 1}`);
|
||||
const minValue = document.getElementById(`minValue${index + 1}`);
|
||||
const maxValue = document.getElementById(`maxValue${index + 1}`);
|
||||
|
||||
if (points.length > 0 && currentValue && minValue && maxValue) {
|
||||
const values = points.map((point) => Number(point.y));
|
||||
const current = values[values.length - 1];
|
||||
const min = Math.min(...values);
|
||||
const max = Math.max(...values);
|
||||
|
||||
currentValue.textContent = current.toFixed(2);
|
||||
minValue.textContent = min.toFixed(2);
|
||||
maxValue.textContent = max.toFixed(2);
|
||||
hasData = true;
|
||||
} else {
|
||||
if (currentValue) {
|
||||
currentValue.textContent = '--';
|
||||
}
|
||||
if (minValue) {
|
||||
minValue.textContent = '--';
|
||||
}
|
||||
if (maxValue) {
|
||||
maxValue.textContent = '--';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const dataPointsElement = document.getElementById('dataPoints');
|
||||
if (dataPointsElement) {
|
||||
dataPointsElement.textContent = totalPoints;
|
||||
}
|
||||
|
||||
const lastUpdateElement = document.getElementById('lastUpdate');
|
||||
if (lastUpdateElement) {
|
||||
lastUpdateElement.textContent = hasData ? moment().format('HH:mm:ss') : '--';
|
||||
}
|
||||
}
|
||||
|
||||
function syncDatasetVisibility() {
|
||||
if (!chart) {
|
||||
return;
|
||||
}
|
||||
|
||||
getTagSelectElements().forEach((select, index) => {
|
||||
const tagName = select.value.trim();
|
||||
const dataset = chart.data.datasets[index];
|
||||
dataset.label = tagName || defaultLabels[index];
|
||||
if (!tagName) {
|
||||
chartData[index] = [];
|
||||
dataset.data = [];
|
||||
dataset.hidden = true;
|
||||
} else if (dataset.data.length === 0) {
|
||||
dataset.hidden = false;
|
||||
}
|
||||
});
|
||||
|
||||
chart.update();
|
||||
updateStatistics();
|
||||
}
|
||||
|
||||
async function fetchTagData(tagName) {
|
||||
if (!tagName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const endTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
const startTime = moment().subtract(timeWindowMinutes, 'minutes').format('YYYY-MM-DD HH:mm:ss');
|
||||
const url = `realtime_data_fivechart.php?tag=${encodeURIComponent(tagName)}&limit=15000&start_time=${encodeURIComponent(startTime)}&end_time=${encodeURIComponent(endTime)}`;
|
||||
|
||||
const response = await fetch(url, { cache: 'no-cache' });
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
if (!payload.success || !Array.isArray(payload.data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const cutoffTime = moment().subtract(timeWindowMinutes, 'minutes').toDate();
|
||||
return payload.data
|
||||
.map((point) => ({
|
||||
x: new Date(point.x),
|
||||
y: point.y
|
||||
}))
|
||||
.filter((point) => point.x >= cutoffTime);
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data for ${tagName}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
const activeTags = getActiveTags();
|
||||
if (activeTags.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(activeTags.map((item) => fetchTagData(item.tagName)));
|
||||
let successCount = 0;
|
||||
|
||||
activeTags.forEach((item, idx) => {
|
||||
const dataset = chart.data.datasets[item.index];
|
||||
const data = results[idx];
|
||||
if (data === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
chartData[item.index] = data;
|
||||
dataset.data = data;
|
||||
dataset.label = item.tagName;
|
||||
dataset.hidden = false;
|
||||
successCount += 1;
|
||||
});
|
||||
|
||||
getTagSelectElements().forEach((select, index) => {
|
||||
if (!select.value) {
|
||||
const dataset = chart.data.datasets[index];
|
||||
chartData[index] = [];
|
||||
dataset.data = [];
|
||||
dataset.label = defaultLabels[index];
|
||||
dataset.hidden = true;
|
||||
}
|
||||
});
|
||||
|
||||
chart.options.scales.x.min = moment().subtract(timeWindowMinutes, 'minutes').toDate();
|
||||
chart.options.scales.x.max = moment().toDate();
|
||||
chart.update('active');
|
||||
|
||||
updateStatistics();
|
||||
updateChartTitle();
|
||||
|
||||
const tagSummary = activeTags.map((item) => item.tagName).join(', ');
|
||||
if (successCount === 0) {
|
||||
updateStatus('disconnected', 'No data returned for the selected tags.');
|
||||
} else if (successCount < activeTags.length) {
|
||||
updateStatus('connected', `Partial update (${successCount}/${activeTags.length}) for ${tagSummary} at ${new Date().toLocaleTimeString()}`);
|
||||
} else {
|
||||
updateStatus('connected', `Connected to ${tagSummary} - Last update ${new Date().toLocaleTimeString()}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
updateStatus('disconnected', `Connection error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function startRealTime() {
|
||||
const activeTags = getActiveTags();
|
||||
if (activeTags.length === 0) {
|
||||
alert('Please select at least one tag to start trending.');
|
||||
return;
|
||||
}
|
||||
|
||||
isRunning = true;
|
||||
const interval = parseInt(document.getElementById('updateInterval').value, 10) || 5000;
|
||||
|
||||
document.getElementById('startBtn').disabled = true;
|
||||
document.getElementById('stopBtn').disabled = false;
|
||||
setControlsDisabled(true);
|
||||
|
||||
fetchData();
|
||||
updateTimer = setInterval(fetchData, interval);
|
||||
|
||||
const tagSummary = activeTags.map((item) => item.tagName).join(', ');
|
||||
updateStatus('connected', `Trending ${tagSummary} - Update every ${interval / 1000}s`);
|
||||
}
|
||||
|
||||
function stopRealTime() {
|
||||
isRunning = false;
|
||||
if (updateTimer) {
|
||||
clearInterval(updateTimer);
|
||||
updateTimer = null;
|
||||
}
|
||||
|
||||
document.getElementById('startBtn').disabled = false;
|
||||
document.getElementById('stopBtn').disabled = true;
|
||||
setControlsDisabled(false);
|
||||
|
||||
updateStatus('disconnected', 'Real-time updates stopped - Select tags and click Start to resume');
|
||||
}
|
||||
|
||||
function clearChart() {
|
||||
if (isRunning) {
|
||||
stopRealTime();
|
||||
}
|
||||
|
||||
chartData = Array.from({ length: MAX_TAGS }, () => []);
|
||||
chart.data.datasets.forEach((dataset, index) => {
|
||||
dataset.data = [];
|
||||
dataset.label = defaultLabels[index];
|
||||
dataset.hidden = index !== 0;
|
||||
});
|
||||
|
||||
chart.update();
|
||||
updateStatistics();
|
||||
updateChartTitle();
|
||||
updateStatus('disconnected', 'Chart cleared - Select tags and click Start to resume');
|
||||
setControlsDisabled(false);
|
||||
}
|
||||
|
||||
document.getElementById('updateInterval').addEventListener('change', () => {
|
||||
if (isRunning) {
|
||||
stopRealTime();
|
||||
setTimeout(startRealTime, 150);
|
||||
}
|
||||
});
|
||||
|
||||
getTagSelectElements().forEach((select) => {
|
||||
select.addEventListener('change', () => {
|
||||
updateChartTitle();
|
||||
if (isRunning) {
|
||||
fetchData();
|
||||
} else {
|
||||
syncDatasetVisibility();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initChart();
|
||||
setTimeout(() => {
|
||||
loadTags().then(() => syncDatasetVisibility());
|
||||
}, 100);
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (updateTimer) {
|
||||
clearInterval(updateTimer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require __DIR__ . '/../../includes/layout/footer.php'; ?>
|
||||
Reference in New Issue
Block a user