Folder reorganize 1
This commit is contained in:
734
trends/live/multichart.php
Normal file
734
trends/live/multichart.php
Normal file
@@ -0,0 +1,734 @@
|
||||
<?php // phpcs:ignoreFile
|
||||
|
||||
require __DIR__ . '/../../session.php';
|
||||
require __DIR__ . '/../../userAccess.php';
|
||||
|
||||
$pageTitle = 'Multi-Chart Trending';
|
||||
$pageSubtitle = 'Watch up to eight live tags at once';
|
||||
$pageDescription = 'Build a custom grid of synchronized charts, then reorder them to match your shift handoff.';
|
||||
$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>Multi-chart dashboard</h2>
|
||||
<p>Start up to four live charts, synchronize update intervals, and drag cards to reorder the layout.</p>
|
||||
</div>
|
||||
<span class="trend-status trend-status--stopped">Manual control</span>
|
||||
</header>
|
||||
|
||||
<div class="trend-global-controls">
|
||||
<div class="trend-control">
|
||||
<label for="globalUpdateInterval">Update interval</label>
|
||||
<select id="globalUpdateInterval">
|
||||
<option value="5000" selected>Every 5 seconds</option>
|
||||
<option value="10000">Every 10 seconds</option>
|
||||
<option value="30000">Every 30 seconds</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="globalTimeWindow">Time window</label>
|
||||
<select id="globalTimeWindow">
|
||||
<option value="10">Last 10 minutes</option>
|
||||
<option value="15" selected>Last 15 minutes</option>
|
||||
<option value="30">Last 30 minutes</option>
|
||||
<option value="60">Last hour</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-actions">
|
||||
<button type="button" class="button button--success" onclick="startAllCharts()">Start all</button>
|
||||
<button type="button" class="button button--danger" onclick="stopAllCharts()">Stop all</button>
|
||||
<button type="button" class="button button--ghost" onclick="clearAllCharts()">Clear all</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="multi-trend-grid" data-multi-trend-grid>
|
||||
<article class="multi-trend-card" id="panel1">
|
||||
<header class="multi-trend-card__header">
|
||||
<h3 class="multi-trend-card__title" id="title1">Chart 1</h3>
|
||||
<span class="trend-status trend-status--stopped" id="status1">STOPPED</span>
|
||||
</header>
|
||||
<div class="multi-trend-card__controls">
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect1_1">Primary tag</label>
|
||||
<select id="tagSelect1_1">
|
||||
<option value="">Loading...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect1_2">Secondary tag</label>
|
||||
<select id="tagSelect1_2">
|
||||
<option value="">Optional...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-actions">
|
||||
<button type="button" class="button button--success" onclick="startChart(1)">Start</button>
|
||||
<button type="button" class="button button--danger" onclick="stopChart(1)">Stop</button>
|
||||
<button type="button" class="button button--ghost" onclick="clearChart(1)">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-stat-grid trend-stat-grid--compact">
|
||||
<div class="trend-stat">
|
||||
<span id="current1_1" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Current 1</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="current1_2" class="trend-stat__value trend-stat__value--secondary">--</span>
|
||||
<span class="trend-stat__label">Current 2</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="points1" class="trend-stat__value">0</span>
|
||||
<span class="trend-stat__label">Points</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="update1" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Updated</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-chart trend-chart--compact">
|
||||
<canvas id="chart1"></canvas>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="multi-trend-card" id="panel2">
|
||||
<header class="multi-trend-card__header">
|
||||
<h3 class="multi-trend-card__title" id="title2">Chart 2</h3>
|
||||
<span class="trend-status trend-status--stopped" id="status2">STOPPED</span>
|
||||
</header>
|
||||
<div class="multi-trend-card__controls">
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect2_1">Primary tag</label>
|
||||
<select id="tagSelect2_1">
|
||||
<option value="">Loading...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect2_2">Secondary tag</label>
|
||||
<select id="tagSelect2_2">
|
||||
<option value="">Optional...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-actions">
|
||||
<button type="button" class="button button--success" onclick="startChart(2)">Start</button>
|
||||
<button type="button" class="button button--danger" onclick="stopChart(2)">Stop</button>
|
||||
<button type="button" class="button button--ghost" onclick="clearChart(2)">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-stat-grid trend-stat-grid--compact">
|
||||
<div class="trend-stat">
|
||||
<span id="current2_1" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Current 1</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="current2_2" class="trend-stat__value trend-stat__value--secondary">--</span>
|
||||
<span class="trend-stat__label">Current 2</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="points2" class="trend-stat__value">0</span>
|
||||
<span class="trend-stat__label">Points</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="update2" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Updated</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-chart trend-chart--compact">
|
||||
<canvas id="chart2"></canvas>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="multi-trend-card" id="panel3">
|
||||
<header class="multi-trend-card__header">
|
||||
<h3 class="multi-trend-card__title" id="title3">Chart 3</h3>
|
||||
<span class="trend-status trend-status--stopped" id="status3">STOPPED</span>
|
||||
</header>
|
||||
<div class="multi-trend-card__controls">
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect3_1">Primary tag</label>
|
||||
<select id="tagSelect3_1">
|
||||
<option value="">Loading...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect3_2">Secondary tag</label>
|
||||
<select id="tagSelect3_2">
|
||||
<option value="">Optional...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-actions">
|
||||
<button type="button" class="button button--success" onclick="startChart(3)">Start</button>
|
||||
<button type="button" class="button button--danger" onclick="stopChart(3)">Stop</button>
|
||||
<button type="button" class="button button--ghost" onclick="clearChart(3)">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-stat-grid trend-stat-grid--compact">
|
||||
<div class="trend-stat">
|
||||
<span id="current3_1" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Current 1</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="current3_2" class="trend-stat__value trend-stat__value--secondary">--</span>
|
||||
<span class="trend-stat__label">Current 2</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="points3" class="trend-stat__value">0</span>
|
||||
<span class="trend-stat__label">Points</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="update3" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Updated</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-chart trend-chart--compact">
|
||||
<canvas id="chart3"></canvas>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="multi-trend-card" id="panel4">
|
||||
<header class="multi-trend-card__header">
|
||||
<h3 class="multi-trend-card__title" id="title4">Chart 4</h3>
|
||||
<span class="trend-status trend-status--stopped" id="status4">STOPPED</span>
|
||||
</header>
|
||||
<div class="multi-trend-card__controls">
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect4_1">Primary tag</label>
|
||||
<select id="tagSelect4_1">
|
||||
<option value="">Loading...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-control">
|
||||
<label for="tagSelect4_2">Secondary tag</label>
|
||||
<select id="tagSelect4_2">
|
||||
<option value="">Optional...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="trend-actions">
|
||||
<button type="button" class="button button--success" onclick="startChart(4)">Start</button>
|
||||
<button type="button" class="button button--danger" onclick="stopChart(4)">Stop</button>
|
||||
<button type="button" class="button button--ghost" onclick="clearChart(4)">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-stat-grid trend-stat-grid--compact">
|
||||
<div class="trend-stat">
|
||||
<span id="current4_1" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Current 1</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="current4_2" class="trend-stat__value trend-stat__value--secondary">--</span>
|
||||
<span class="trend-stat__label">Current 2</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="points4" class="trend-stat__value">0</span>
|
||||
<span class="trend-stat__label">Points</span>
|
||||
</div>
|
||||
<div class="trend-stat">
|
||||
<span id="update4" class="trend-stat__value">--</span>
|
||||
<span class="trend-stat__label">Updated</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-chart trend-chart--compact">
|
||||
<canvas id="chart4"></canvas>
|
||||
</div>
|
||||
</article>
|
||||
</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 src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@2.0.1/dist/chartjs-plugin-zoom.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
|
||||
|
||||
<script>
|
||||
// Global variables for multi-chart management
|
||||
const charts = {};
|
||||
const chartData = {};
|
||||
const updateTimers = {};
|
||||
const chartStates = {};
|
||||
let globalTimeWindow = 15;
|
||||
let globalUpdateInterval = 5000;
|
||||
let availableTags = [];
|
||||
|
||||
// Chart configuration template
|
||||
function getChartConfig(chartId) {
|
||||
return {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [{
|
||||
label: 'Primary Tag',
|
||||
data: [],
|
||||
borderColor: '#3498db',
|
||||
backgroundColor: 'rgba(52, 152, 219, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 0,
|
||||
hitRadius: 0,
|
||||
yAxisID: 'y1'
|
||||
}, {
|
||||
label: 'Secondary Tag',
|
||||
data: [],
|
||||
borderColor: '#27ae60',
|
||||
backgroundColor: 'rgba(39, 174, 96, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
pointRadius: 0,
|
||||
pointHoverRadius: 0,
|
||||
hitRadius: 0,
|
||||
yAxisID: 'y2',
|
||||
hidden: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
duration: 200
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index'
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
displayFormats: {
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: `Last ${globalTimeWindow} Minutes`,
|
||||
font: { size: 10 }
|
||||
},
|
||||
min: function() {
|
||||
return moment().subtract(globalTimeWindow, 'minutes').toDate();
|
||||
},
|
||||
max: function() {
|
||||
return moment().toDate();
|
||||
},
|
||||
ticks: {
|
||||
font: { size: 9 }
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Primary',
|
||||
color: '#3498db',
|
||||
font: { size: 10 }
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: true,
|
||||
},
|
||||
ticks: {
|
||||
color: '#3498db',
|
||||
font: { size: 9 }
|
||||
}
|
||||
},
|
||||
y2: {
|
||||
type: 'linear',
|
||||
display: false,
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Secondary',
|
||||
color: '#27ae60',
|
||||
font: { size: 10 }
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
color: '#27ae60',
|
||||
font: { size: 9 }
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top',
|
||||
labels: {
|
||||
font: { size: 10 },
|
||||
usePointStyle: true
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
titleFont: { size: 11 },
|
||||
bodyFont: { size: 10 },
|
||||
callbacks: {
|
||||
title: function(context) {
|
||||
return moment(context[0].parsed.x).format('HH:mm:ss');
|
||||
},
|
||||
label: function(context) {
|
||||
return `${context.dataset.label}: ${context.parsed.y.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
zoom: {
|
||||
pan: {
|
||||
enabled: true,
|
||||
modifierKey: 'ctrl',
|
||||
mode: 'x'
|
||||
},
|
||||
zoom: {
|
||||
wheel: { enabled: true, modifierKey: 'shift' },
|
||||
pinch: { enabled: true },
|
||||
mode: 'x'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize all charts
|
||||
function initializeCharts() {
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const ctx = document.getElementById(`chart${i}`).getContext('2d');
|
||||
charts[i] = new Chart(ctx, getChartConfig(i));
|
||||
chartData[i] = { primary: [], secondary: [] };
|
||||
chartStates[i] = { running: false, timer: null };
|
||||
}
|
||||
}
|
||||
|
||||
// Load tags for all dropdowns
|
||||
async function loadTags() {
|
||||
const pairs = [];
|
||||
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const primary = document.getElementById(`tagSelect${i}_1`);
|
||||
const secondary = document.getElementById(`tagSelect${i}_2`);
|
||||
|
||||
if (!primary || !secondary) {
|
||||
console.error(`Missing tag selectors for chart ${i}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
primary.innerHTML = '<option value="">Loading tags...</option>';
|
||||
secondary.innerHTML = '<option value="">Loading tags...</option>';
|
||||
pairs.push({ primary, secondary });
|
||||
}
|
||||
|
||||
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 data = await response.json();
|
||||
|
||||
if (!(data.success && Array.isArray(data.tags) && data.tags.length > 0)) {
|
||||
throw new Error(data.error || 'No tags available');
|
||||
}
|
||||
|
||||
availableTags = data.tags;
|
||||
|
||||
pairs.forEach(({ primary, secondary }) => {
|
||||
primary.innerHTML = '<option value="">Select primary tag...</option>';
|
||||
secondary.innerHTML = '<option value="">Select secondary tag...</option>';
|
||||
|
||||
availableTags.forEach((tag) => {
|
||||
if (!tag || !tag.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const option1 = document.createElement('option');
|
||||
option1.value = tag.name;
|
||||
option1.textContent = tag.name;
|
||||
primary.appendChild(option1);
|
||||
|
||||
const option2 = document.createElement('option');
|
||||
option2.value = tag.name;
|
||||
option2.textContent = tag.name;
|
||||
secondary.appendChild(option2);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to load tags for multi-chart:', error);
|
||||
pairs.forEach(({ primary, secondary }) => {
|
||||
const message = `Error: ${error.message}`;
|
||||
primary.innerHTML = `<option value="">${message}</option>`;
|
||||
secondary.innerHTML = `<option value="">${message}</option>`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch data for a specific chart
|
||||
async function fetchChartData(chartId) {
|
||||
const tag1 = document.getElementById(`tagSelect${chartId}_1`).value;
|
||||
const tag2 = document.getElementById(`tagSelect${chartId}_2`).value;
|
||||
|
||||
if (!tag1) return;
|
||||
|
||||
try {
|
||||
const endTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
const startTime = moment().subtract(globalTimeWindow, 'minutes').format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
// Fetch primary tag data
|
||||
const data1 = await fetchTagData(tag1);
|
||||
if (data1) {
|
||||
chartData[chartId].primary = data1;
|
||||
charts[chartId].data.datasets[0].data = data1;
|
||||
charts[chartId].data.datasets[0].label = tag1;
|
||||
}
|
||||
|
||||
// Fetch secondary tag data if selected
|
||||
if (tag2 && tag2 !== tag1) {
|
||||
const data2 = await fetchTagData(tag2);
|
||||
if (data2) {
|
||||
chartData[chartId].secondary = data2;
|
||||
charts[chartId].data.datasets[1].data = data2;
|
||||
charts[chartId].data.datasets[1].label = tag2;
|
||||
charts[chartId].data.datasets[1].hidden = false;
|
||||
charts[chartId].options.scales.y2.display = true;
|
||||
}
|
||||
} else {
|
||||
chartData[chartId].secondary = [];
|
||||
charts[chartId].data.datasets[1].data = [];
|
||||
charts[chartId].data.datasets[1].hidden = true;
|
||||
charts[chartId].options.scales.y2.display = false;
|
||||
}
|
||||
|
||||
// Update time axis
|
||||
charts[chartId].options.scales.x.min = moment().subtract(globalTimeWindow, 'minutes').toDate();
|
||||
charts[chartId].options.scales.x.max = moment().toDate();
|
||||
charts[chartId].options.scales.x.title.text = `Last ${globalTimeWindow} Minutes`;
|
||||
|
||||
charts[chartId].update('none');
|
||||
updateChartStats(chartId);
|
||||
updateChartTitle(chartId);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data for chart ${chartId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch data for individual tag
|
||||
async function fetchTagData(tagName) {
|
||||
try {
|
||||
const endTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||
const startTime = moment().subtract(globalTimeWindow, 'minutes').format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
const url = `realtime_data.php?tag=${encodeURIComponent(tagName)}&limit=7200&start_time=${encodeURIComponent(startTime)}&end_time=${encodeURIComponent(endTime)}`;
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.data && data.data.length > 0) {
|
||||
return data.data.map(point => ({
|
||||
x: new Date(point.x),
|
||||
y: point.y
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(`Error fetching data for ${tagName}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Update chart statistics
|
||||
function updateChartStats(chartId) {
|
||||
const primary = chartData[chartId].primary;
|
||||
const secondary = chartData[chartId].secondary;
|
||||
|
||||
// Primary tag stats
|
||||
if (primary.length > 0) {
|
||||
const current = primary[primary.length - 1].y;
|
||||
document.getElementById(`current${chartId}_1`).textContent = current.toFixed(2);
|
||||
} else {
|
||||
document.getElementById(`current${chartId}_1`).textContent = '--';
|
||||
}
|
||||
|
||||
// Secondary tag stats
|
||||
if (secondary.length > 0) {
|
||||
const current = secondary[secondary.length - 1].y;
|
||||
document.getElementById(`current${chartId}_2`).textContent = current.toFixed(2);
|
||||
} else {
|
||||
document.getElementById(`current${chartId}_2`).textContent = '--';
|
||||
}
|
||||
|
||||
// General stats
|
||||
document.getElementById(`points${chartId}`).textContent = primary.length + secondary.length;
|
||||
document.getElementById(`update${chartId}`).textContent = moment().format('HH:mm:ss');
|
||||
}
|
||||
|
||||
// Update chart title
|
||||
function updateChartTitle(chartId) {
|
||||
const tag1 = document.getElementById(`tagSelect${chartId}_1`).value;
|
||||
const tag2 = document.getElementById(`tagSelect${chartId}_2`).value;
|
||||
|
||||
let title = `Chart ${chartId}`;
|
||||
if (tag1) {
|
||||
title += ` - ${tag1}`;
|
||||
if (tag2) {
|
||||
title += ` & ${tag2}`;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById(`title${chartId}`).textContent = title;
|
||||
}
|
||||
|
||||
// Start individual chart
|
||||
function startChart(chartId) {
|
||||
const tag1 = document.getElementById(`tagSelect${chartId}_1`).value;
|
||||
if (!tag1) {
|
||||
alert(`Please select a primary tag for Chart ${chartId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chartStates[chartId].running) return;
|
||||
|
||||
chartStates[chartId].running = true;
|
||||
const statusBadge = document.getElementById(`status${chartId}`);
|
||||
if (statusBadge) {
|
||||
statusBadge.textContent = 'RUNNING';
|
||||
statusBadge.className = 'trend-status trend-status--running';
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
fetchChartData(chartId);
|
||||
|
||||
// Start timer
|
||||
chartStates[chartId].timer = setInterval(() => {
|
||||
fetchChartData(chartId);
|
||||
}, globalUpdateInterval);
|
||||
|
||||
}
|
||||
|
||||
// Stop individual chart
|
||||
function stopChart(chartId) {
|
||||
if (!chartStates[chartId].running) return;
|
||||
|
||||
chartStates[chartId].running = false;
|
||||
const statusBadge = document.getElementById(`status${chartId}`);
|
||||
if (statusBadge) {
|
||||
statusBadge.textContent = 'STOPPED';
|
||||
statusBadge.className = 'trend-status trend-status--stopped';
|
||||
}
|
||||
|
||||
if (chartStates[chartId].timer) {
|
||||
clearInterval(chartStates[chartId].timer);
|
||||
chartStates[chartId].timer = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clear individual chart
|
||||
function clearChart(chartId) {
|
||||
stopChart(chartId);
|
||||
|
||||
chartData[chartId] = { primary: [], secondary: [] };
|
||||
charts[chartId].data.datasets[0].data = [];
|
||||
charts[chartId].data.datasets[1].data = [];
|
||||
charts[chartId].data.datasets[1].hidden = true;
|
||||
charts[chartId].options.scales.y2.display = false;
|
||||
charts[chartId].update();
|
||||
|
||||
// Reset stats
|
||||
document.getElementById(`current${chartId}_1`).textContent = '--';
|
||||
document.getElementById(`current${chartId}_2`).textContent = '--';
|
||||
document.getElementById(`points${chartId}`).textContent = '0';
|
||||
document.getElementById(`update${chartId}`).textContent = '--';
|
||||
|
||||
// Reset title
|
||||
document.getElementById(`title${chartId}`).textContent = `Chart ${chartId}`;
|
||||
}
|
||||
|
||||
// Global controls
|
||||
function startAllCharts() {
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
const tag1 = document.getElementById(`tagSelect${i}_1`).value;
|
||||
if (tag1) {
|
||||
startChart(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopAllCharts() {
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
stopChart(i);
|
||||
}
|
||||
}
|
||||
|
||||
function clearAllCharts() {
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
clearChart(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle global setting changes
|
||||
document.getElementById('globalUpdateInterval').addEventListener('change', function() {
|
||||
globalUpdateInterval = parseInt(this.value);
|
||||
|
||||
// Update all running charts
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
if (chartStates[i].running) {
|
||||
stopChart(i);
|
||||
setTimeout(() => startChart(i), 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('globalTimeWindow').addEventListener('change', function() {
|
||||
globalTimeWindow = parseInt(this.value);
|
||||
|
||||
// Update all charts
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
charts[i].options.scales.x.title.text = `Last ${globalTimeWindow} Minutes`;
|
||||
if (chartStates[i].running) {
|
||||
fetchChartData(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize everything on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeCharts();
|
||||
loadTags();
|
||||
|
||||
const grid = document.querySelector('[data-multi-trend-grid]');
|
||||
if (!grid) {
|
||||
console.warn('Multi-trend grid not found for drag-and-drop.');
|
||||
return;
|
||||
}
|
||||
|
||||
Sortable.create(grid, {
|
||||
animation: 150,
|
||||
handle: '.multi-trend-card__header',
|
||||
draggable: '.multi-trend-card'
|
||||
});
|
||||
});
|
||||
|
||||
// Cleanup on page unload
|
||||
window.addEventListener('beforeunload', function() {
|
||||
stopAllCharts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require __DIR__ . '/../../includes/layout/footer.php'; ?>
|
||||
Reference in New Issue
Block a user