Files
meshview/meshview/templates/node_graphs.html

185 lines
5.8 KiB
HTML

{% macro graph(name) %}
<div id="{{name}}Chart" style="width: 100%; height: 100%;"></div>
{% endmacro %}
<!-- Buttons for Download CSV and Expand Modal -->
<div class="d-flex justify-content-end mb-2">
<button class="btn btn-sm btn-outline-light me-2" id="downloadCsvBtn">Download CSV</button>
<button class="btn btn-sm btn-outline-light" data-bs-toggle="modal" data-bs-target="#fullChartModal">Expand</button>
</div>
<!-- Tabs -->
<ul class="nav nav-tabs" role="tablist">
{% for name in [
"power", "chutil", "temperature", "humidity", "pressure",
"iaq", "wind_speed", "wind_direction", "power_metrics", "neighbors"
] %}
<li class="nav-item" role="presentation">
<button class="nav-link {% if loop.first %}active{% endif %}" data-bs-toggle="tab" data-bs-target="#{{name}}Tab" type="button" role="tab">{{ name | capitalize }}</button>
</li>
{% endfor %}
</ul>
<!-- Tab content -->
<div class="tab-content mt-3" style="height: 40vh;">
{% for name in [
"power", "chutil", "temperature", "humidity", "pressure",
"iaq", "wind_speed", "wind_direction", "power_metrics", "neighbors"
] %}
<div class="tab-pane fade {% if loop.first %}show active{% endif %}" id="{{name}}Tab" role="tabpanel" style="height: 100%;">
{{ graph(name) | safe }}
</div>
{% endfor %}
</div>
<!-- Fullscreen Modal for graphs -->
<div class="modal fade" id="fullChartModal" tabindex="-1" aria-labelledby="fullChartModalLabel" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="fullChartModalLabel">Full Graph</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="height: 100vh;">
<div id="fullChartContainer" style="width: 100%; height: 100%;"></div>
</div>
</div>
</div>
</div>
<!-- ECharts Library -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
let currentChart = null;
let currentChartName = null;
let currentChartData = null;
async function loadChart(name) {
currentChartName = name;
const chartDiv = document.getElementById(`${name}Chart`);
if (!chartDiv) return;
try {
const resp = await fetch(`/graph/${name}_json/{{ node_id }}`);
if (!resp.ok) throw new Error(`Failed to load data for ${name}`);
const data = await resp.json();
currentChartData = data;
if (!currentChart) {
currentChart = echarts.init(chartDiv);
} else if (currentChart.getDom() !== chartDiv) {
currentChart.dispose();
currentChart = echarts.init(chartDiv);
}
currentChart.setOption({
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: data.timestamps,
axisLabel: { color: '#fff' },
},
yAxis: {
type: 'value',
axisLabel: { color: '#fff' },
},
series: data.series.map(s => ({
name: s.name,
type: 'line',
data: s.data,
smooth: true,
connectNulls: true,
})),
legend: { textStyle: { color: '#fff' } }
});
} catch (err) {
console.error(err);
currentChartData = null;
currentChartName = null;
}
}
// Load first tab chart initially
const firstTabBtn = document.querySelector('.nav-tabs button.nav-link.active');
if (firstTabBtn) {
loadChart(firstTabBtn.textContent.toLowerCase());
}
// Listen for tab changes to load charts
document.querySelectorAll('.nav-tabs button.nav-link').forEach(button => {
button.addEventListener('shown.bs.tab', event => {
const tabName = event.target.textContent.toLowerCase();
loadChart(tabName);
});
});
// Download CSV button handler for current chart
document.getElementById('downloadCsvBtn').addEventListener('click', () => {
if (!currentChartData || !currentChartName) {
alert("Chart data not loaded yet.");
return;
}
const { timestamps, series } = currentChartData;
let csv = 'Time,' + series.map(s => s.name).join(',') + '\n';
for (let i = 0; i < timestamps.length; i++) {
const row = [timestamps[i]];
for (const s of series) {
row.push(s.data[i] != null ? s.data[i] : '');
}
csv += row.join(',') + '\n';
}
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${currentChartName}_{{ node_id }}.csv`;
a.click();
URL.revokeObjectURL(url);
});
// Expand modal logic
const fullChartContainer = document.getElementById('fullChartContainer');
let fullChart = null;
const modal = document.getElementById('fullChartModal');
modal.addEventListener('shown.bs.modal', () => {
if (!currentChartData || !currentChartName) return;
if (!fullChart) {
fullChart = echarts.init(fullChartContainer);
}
fullChart.setOption({
title: { text: currentChartName.charAt(0).toUpperCase() + currentChartName.slice(1), textStyle: { color: '#fff' } },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: currentChartData.timestamps,
axisLabel: { color: '#fff' },
},
yAxis: {
type: 'value',
axisLabel: { color: '#fff' },
},
series: currentChartData.series.map(s => ({
name: s.name,
type: 'line',
data: s.data,
smooth: true,
connectNulls: true,
})),
legend: { textStyle: { color: '#fff' } }
});
fullChart.resize();
});
window.addEventListener('resize', () => {
if (fullChart) fullChart.resize();
});
});
</script>