707 lines
26 KiB
HTML
707 lines
26 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>测斜仪数据</title>
|
||
<script src="/static/js/chart.js"></script>
|
||
<link rel="stylesheet" href="/static/css/tailwind.min.css">
|
||
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
.content-narrow {
|
||
width: 85%;
|
||
max-width: 1200px;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
@media (max-width: 768px) {
|
||
.content-narrow { width: 92%; }
|
||
}
|
||
|
||
.header {
|
||
padding: 10px;
|
||
text-align: center;
|
||
border-bottom: 1px solid #ddd;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 15px;
|
||
}
|
||
|
||
.controls {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.control-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.control-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
select, input, button {
|
||
padding: 5px 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
button {
|
||
background-color: #007bff;
|
||
color: white;
|
||
border: none;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
button:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
|
||
/* 覆盖设备列表按钮的全局 button 样式,确保文字不是白色且背景为白色 */
|
||
#deviceListInline button {
|
||
background-color: #ffffff;
|
||
color: #1f2937; /* tailwind: text-gray-800 */
|
||
}
|
||
#deviceListInline button:hover {
|
||
background-color: #f9fafb; /* tailwind: hover:bg-gray-50 */
|
||
}
|
||
|
||
.chart-container {
|
||
margin-bottom: 20px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 15px;
|
||
background-color: #fff;
|
||
}
|
||
|
||
canvas {
|
||
width: 100%;
|
||
max-height: 400px;
|
||
}
|
||
|
||
.table-container {
|
||
overflow-x: auto;
|
||
margin-top: 20px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background-color: #fff;
|
||
}
|
||
h2{
|
||
text-align:center;
|
||
color: #333;
|
||
margin-top: 0;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
th, td {
|
||
border: 1px solid #ddd;
|
||
padding: 12px 8px;
|
||
text-align: left;
|
||
}
|
||
|
||
th {
|
||
background-color: #f8f9fa;
|
||
font-weight: bold;
|
||
}
|
||
|
||
tr:nth-child(even) {
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
tr:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.footer {
|
||
text-align: center;
|
||
padding: 10px;
|
||
border-top: 1px solid #ddd;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.controls {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.control-group {
|
||
width: 100%;
|
||
}
|
||
|
||
select, input {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="text-[14px] md:text-[15px]">
|
||
<div class="header p-2 text-center border-b border-gray-200">
|
||
<h1 class="text-2xl md:text-3xl font-semibold py-7">测斜仪数据</h1>
|
||
|
||
</div>
|
||
|
||
<div class="content-narrow py-5">
|
||
<!-- 设备列表(内联显示) -->
|
||
<div id="devicesInline" class="mb-5 p-3 border rounded bg-white">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<div class="flex items-center gap-4">
|
||
<strong class="text-sm text-gray-700">设备列表</strong>
|
||
|
||
</div>
|
||
<span class="text-xs text-gray-600">最后一次在线时间</span>
|
||
</div>
|
||
<div id="deviceListInline" class="flex flex-col gap-2"></div>
|
||
</div>
|
||
|
||
<div class="controls flex flex-col gap-4 mb-5 p-4 border rounded bg-white">
|
||
|
||
<div class="control-row">
|
||
<div class="control-group">
|
||
<label for="deviceInput" class="text-sm text-gray-600">设备编号:</label>
|
||
<input id="deviceInput" type="text" placeholder="" class="px-2 py-1 border border-gray-300 rounded text-sm font-mono" />
|
||
<button id="findDeviceBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-sm">搜索</button>
|
||
</div>
|
||
<div class="control-group">
|
||
<label for="sensorSelect" class="text-sm text-gray-600">选择探头:</label>
|
||
<select id="sensorSelect" class="px-2 py-1 border border-gray-300 rounded text-sm">
|
||
<option value="all">所有探头</option>
|
||
</select>
|
||
</div>
|
||
<div class="control-group">
|
||
<label for="metricSelect" class="text-sm text-gray-600">图例:</label>
|
||
<select id="metricSelect" class="px-2 py-1 border border-gray-300 rounded text-sm">
|
||
<option value="x" selected>X 轴</option>
|
||
<option value="y">Y 轴</option>
|
||
<option value="temperature">温度</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-row">
|
||
<div class="control-group">
|
||
<label for="limitSelect" class="text-sm text-gray-600">显示记录数:</label>
|
||
<select id="limitSelect" class="px-2 py-1 border border-gray-300 rounded text-sm">
|
||
<option value="100">100</option>
|
||
<option value="200">200</option>
|
||
<option value="400">400</option>
|
||
<option value="800">800</option>
|
||
<option value="1600">1600</option>
|
||
<option value="all" selected>全部</option>
|
||
</select>
|
||
</div>
|
||
<div class="control-group">
|
||
<label for="startDate" class="text-sm text-gray-600">开始时间:</label>
|
||
<input type="datetime-local" id="startDate" class="px-2 py-1 border border-gray-300 rounded text-sm">
|
||
</div>
|
||
<div class="control-group">
|
||
<label for="endDate" class="text-sm text-gray-600">结束时间:</label>
|
||
<input type="datetime-local" id="endDate" class="px-2 py-1 border border-gray-300 rounded text-sm">
|
||
</div>
|
||
<div class="control-group">
|
||
<button id="queryBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-sm">查询数据</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<!-- 图表区域 -->
|
||
<div class="chart-container rounded-md">
|
||
<!-- <h2>传感器数据图表</h2>-->
|
||
<canvas id="sensorChart"></canvas>
|
||
</div>
|
||
|
||
<!-- 数据表格 -->
|
||
<div class="table-container">
|
||
<table id="dataTable" class="min-w-full text-sm text-center">
|
||
<thead>
|
||
<tr>
|
||
<th>数据编号</th>
|
||
<th>探头地址</th>
|
||
<th>时间</th>
|
||
<th>X</th>
|
||
<th>Y</th>
|
||
<th>温度(°C)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tableBody">
|
||
<!-- 数据将通过JavaScript动态添加 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 内联JavaScript -->
|
||
<script>
|
||
|
||
let sensorChart = null;
|
||
let allSensors = [];
|
||
let currentSensorData = [];
|
||
|
||
let devices = [];
|
||
let selectedDeviceID = '';
|
||
let selectedMetric = 'x';
|
||
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initializeDatePickers();
|
||
setupEventListeners();
|
||
loadDevices();
|
||
});
|
||
|
||
|
||
function initializeDatePickers() {
|
||
const now = new Date();
|
||
const tenDaysMs = 10 * 24 * 60 * 60 * 1000;
|
||
const begin = new Date(now.getTime() - tenDaysMs);
|
||
|
||
function fmt(dt) {
|
||
const y = dt.getFullYear();
|
||
const m = (dt.getMonth() + 1).toString().padStart(2, '0');
|
||
const d = dt.getDate().toString().padStart(2, '0');
|
||
const hh = dt.getHours().toString().padStart(2, '0');
|
||
const mm = dt.getMinutes().toString().padStart(2, '0');
|
||
return `${y}-${m}-${d}T${hh}:${mm}`;
|
||
}
|
||
|
||
document.getElementById('startDate').value = fmt(begin);
|
||
document.getElementById('endDate').value = fmt(now);
|
||
}
|
||
|
||
function setupEventListeners() {
|
||
|
||
document.getElementById('queryBtn').addEventListener('click', function() {
|
||
loadData();
|
||
});
|
||
|
||
document.getElementById('sensorSelect').addEventListener('change', function() {
|
||
loadData();
|
||
});
|
||
|
||
const deviceInput = document.getElementById('deviceInput');
|
||
const findBtn = document.getElementById('findDeviceBtn');
|
||
if (findBtn) findBtn.addEventListener('click', findDeviceByInput);
|
||
if (deviceInput) deviceInput.addEventListener('keydown', function(e){ if (e.key === 'Enter') { findDeviceByInput(); }});
|
||
|
||
const metricSelect = document.getElementById('metricSelect');
|
||
if (metricSelect) metricSelect.addEventListener('change', function(){ selectedMetric = this.value; updateChart(currentSensorData); });
|
||
|
||
// 统计信息已融合到设备列表区域,无需额外滚动链接
|
||
}
|
||
|
||
function findDeviceByInput() {
|
||
const input = document.getElementById('deviceInput');
|
||
if (!input) return;
|
||
const v = (input.value || '').trim();
|
||
if (!v) { alert('请输入设备编号'); return; }
|
||
if (!devices || devices.length === 0) {
|
||
|
||
loadDevices();
|
||
}
|
||
|
||
const match = devices.find(d => (d.device_id || '').toLowerCase() === v.toLowerCase());
|
||
if (!match) { alert('未找到该设备'); return; }
|
||
selectedDeviceID = match.device_id;
|
||
|
||
loadSensors();
|
||
}
|
||
|
||
|
||
|
||
function loadSensors() {
|
||
if (!selectedDeviceID) return;
|
||
fetch(`/api/sensors?device_id=${encodeURIComponent(selectedDeviceID)}`)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('获取传感器列表失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
allSensors = data;
|
||
updateSensorSelect(data);
|
||
|
||
|
||
loadData();
|
||
})
|
||
.catch(error => {
|
||
console.error('加载传感器列表出错:', error);
|
||
alert('加载传感器列表出错: ' + error.message);
|
||
});
|
||
}
|
||
|
||
|
||
function updateSensorSelect(sensors) {
|
||
const select = document.getElementById('sensorSelect');
|
||
|
||
|
||
const allOption = select.querySelector('option[value="all"]');
|
||
select.innerHTML = '';
|
||
select.appendChild(allOption);
|
||
|
||
if (sensors.length === 0) {
|
||
const option = document.createElement('option');
|
||
option.value = '';
|
||
option.textContent = '没有可用的传感器';
|
||
select.appendChild(option);
|
||
return;
|
||
}
|
||
|
||
sensors.forEach(id => {
|
||
const option = document.createElement('option');
|
||
option.value = id;
|
||
option.textContent = `探头 ${id}`;
|
||
select.appendChild(option);
|
||
});
|
||
}
|
||
|
||
|
||
function loadData() {
|
||
if (!selectedDeviceID) { alert('请先选择设备'); return; }
|
||
const sensorID = document.getElementById('sensorSelect').value;
|
||
const limit = document.getElementById('limitSelect').value;
|
||
const startDate = document.getElementById('startDate').value;
|
||
const endDate = document.getElementById('endDate').value;
|
||
|
||
let url = '/api/data?';
|
||
let params = [];
|
||
|
||
|
||
params.push(`device_id=${encodeURIComponent(selectedDeviceID)}`);
|
||
if (sensorID !== 'all') {
|
||
params.push(`sensor_id=${sensorID}`);
|
||
}
|
||
|
||
|
||
params.push(`limit=${limit}`);
|
||
|
||
if (startDate) {
|
||
params.push(`start_date=${encodeURIComponent(startDate)}`);
|
||
}
|
||
|
||
if (endDate) {
|
||
params.push(`end_date=${encodeURIComponent(endDate)}`);
|
||
}
|
||
|
||
url += params.join('&');
|
||
|
||
|
||
document.getElementById('queryBtn').textContent = '加载中...';
|
||
|
||
fetch(url)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('获取传感器数据失败');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
currentSensorData = data;
|
||
updateTable(data);
|
||
updateChart(data);
|
||
document.getElementById('queryBtn').textContent = '查询数据';
|
||
})
|
||
.catch(error => {
|
||
console.error('加载数据出错:', error);
|
||
alert('加载数据出错: ' + error.message);
|
||
document.getElementById('queryBtn').textContent = '查询数据';
|
||
});
|
||
}
|
||
|
||
// 渲染内联设备列表(按最后上报排序,新→旧)
|
||
function renderDeviceListInline() {
|
||
const container = document.getElementById('deviceListInline');
|
||
if (!container) return;
|
||
container.innerHTML = '';
|
||
if (!devices || devices.length === 0) {
|
||
const empty = document.createElement('div');
|
||
empty.className = 'text-gray-500 text-sm';
|
||
empty.textContent = '没有设备';
|
||
container.appendChild(empty);
|
||
return;
|
||
}
|
||
|
||
const view = devices.slice().sort((a,b) => {
|
||
const ta = a.last_seen ? new Date(a.last_seen).getTime() : 0;
|
||
const tb = b.last_seen ? new Date(b.last_seen).getTime() : 0;
|
||
return tb - ta; // 新→旧
|
||
});
|
||
|
||
view.forEach(d => {
|
||
const onlineDot = d.online ? '<span class=\"inline-block w-2 h-2 rounded-full bg-green-500\"></span>' : '<span class=\"inline-block w-2 h-2 rounded-full bg-gray-400\"></span>';
|
||
const lastSeen = (() => {
|
||
if (!d.last_seen) return '—';
|
||
const dt = new Date(d.last_seen);
|
||
const y = dt.getFullYear();
|
||
const m = (dt.getMonth()+1).toString().padStart(2,'0');
|
||
const day = dt.getDate().toString().padStart(2,'0');
|
||
const hh = dt.getHours().toString().padStart(2,'0');
|
||
const mm = dt.getMinutes().toString().padStart(2,'0');
|
||
const ss = dt.getSeconds().toString().padStart(2,'0');
|
||
return `${y}/${m}/${day} ${hh}:${mm}:${ss}`;
|
||
})();
|
||
const isSelected = d.device_id === selectedDeviceID;
|
||
|
||
const item = document.createElement('button');
|
||
item.type = 'button';
|
||
// 一行一个,左右布局:左侧设备信息,右侧最后在线时间
|
||
item.className = 'w-full px-3 py-2 border rounded text-sm bg-white hover:bg-gray-50 flex items-center justify-between transition text-gray-800 ' + (isSelected ? 'ring-2 ring-blue-400' : '');
|
||
const leftHtml = `<span class=\"flex items-center gap-2\">${onlineDot}
|
||
<span class=\"font-mono\">${d.device_id}</span>
|
||
<span class=\"text-xs text-gray-600\">(探头: ${d.sensor_count})</span></span>`;
|
||
const rightHtml = `<span class=\"text-xs text-gray-700 font-mono\">${lastSeen}</span>`;
|
||
item.innerHTML = leftHtml + rightHtml;
|
||
item.addEventListener('click', () => {
|
||
selectedDeviceID = d.device_id;
|
||
const input = document.getElementById('deviceInput');
|
||
if (input) input.value = selectedDeviceID;
|
||
renderDeviceListInline();
|
||
loadSensors();
|
||
});
|
||
container.appendChild(item);
|
||
});
|
||
}
|
||
// (原)updateDeviceList 已移除,改用 renderDeviceListInline()
|
||
function loadDevices() {
|
||
fetch('/api/devices')
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
devices = data || [];
|
||
const total = document.getElementById('totalDevices');
|
||
if (total) total.textContent = devices.length;
|
||
const onlineEl = document.getElementById('onlineDevices');
|
||
if (onlineEl) onlineEl.textContent = devices.filter(d => d.online).length;
|
||
renderDeviceListInline();
|
||
|
||
// 默认选择 1513343 设备(若存在)并自动加载
|
||
if (!selectedDeviceID) {
|
||
const def = devices.find(d => (d.device_id || '').toLowerCase() === '1513343');
|
||
if (def) {
|
||
selectedDeviceID = def.device_id;
|
||
const input = document.getElementById('deviceInput');
|
||
if (input) input.value = selectedDeviceID;
|
||
loadSensors();
|
||
}
|
||
}
|
||
})
|
||
.catch(err => console.error('加载设备失败:', err));
|
||
}
|
||
|
||
|
||
function updateTable(data) {
|
||
const tableBody = document.getElementById('tableBody');
|
||
tableBody.innerHTML = '';
|
||
|
||
if (data.length === 0) {
|
||
const row = document.createElement('tr');
|
||
row.innerHTML = '<td colspan="6" style="text-align: center;">没有数据</td>';
|
||
tableBody.appendChild(row);
|
||
return;
|
||
}
|
||
|
||
data.forEach(item => {
|
||
const row = document.createElement('tr');
|
||
|
||
|
||
const date = new Date(item.timestamp);
|
||
|
||
|
||
const formattedDate =
|
||
date.getFullYear() + '/' +
|
||
(date.getMonth() + 1).toString().padStart(2, '0') + '/' +
|
||
date.getDate().toString().padStart(2, '0') + ' ' +
|
||
date.getHours().toString().padStart(2, '0') + ':' +
|
||
date.getMinutes().toString().padStart(2, '0') + ':' +
|
||
date.getSeconds().toString().padStart(2, '0');
|
||
|
||
row.innerHTML =
|
||
'<td>' + item.id + '</td>' +
|
||
'<td>' + item.sensor_id + '</td>' +
|
||
'<td>' + formattedDate + '</td>' +
|
||
'<td>' + item.x.toFixed(3) + '</td>' +
|
||
'<td>' + item.y.toFixed(3) + '</td>' +
|
||
'<td>' + item.temperature.toFixed(1) + '</td>';
|
||
|
||
tableBody.appendChild(row);
|
||
});
|
||
}
|
||
|
||
|
||
function updateChart(data) {
|
||
|
||
const chartData = prepareChartData(data);
|
||
const sensorSelect = document.getElementById('sensorSelect');
|
||
const singleSensorSelected = sensorSelect && sensorSelect.value !== 'all';
|
||
const stats = singleSensorSelected ? computeCurrentStddev(data) : { stddev: 0, count: 0, min: null, max: null };
|
||
const fmt = (v) => (selectedMetric === 'temperature' ? v.toFixed(1) : v.toFixed(3));
|
||
const stdText = (singleSensorSelected && stats.count > 1)
|
||
? `最大: ${fmt(stats.max)} 最小: ${fmt(stats.min)} 标准差: ${fmt(stats.stddev)} (n=${stats.count})`
|
||
: '';
|
||
|
||
|
||
if (sensorChart) {
|
||
sensorChart.destroy();
|
||
}
|
||
|
||
|
||
const ctx = document.getElementById('sensorChart').getContext('2d');
|
||
|
||
|
||
sensorChart = new Chart(ctx, {
|
||
type: 'line',
|
||
data: chartData,
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
title: {
|
||
display: true,
|
||
text: (selectedMetric === 'temperature' ? '温度' : selectedMetric.toUpperCase() + ' 轴')
|
||
},
|
||
subtitle: {
|
||
display: singleSensorSelected && stdText !== '',
|
||
text: stdText,
|
||
align: 'end',
|
||
color: '#666',
|
||
padding: {bottom: 8},
|
||
font: {size: 12}
|
||
},
|
||
tooltip: {
|
||
callbacks: {
|
||
label: function(context) {
|
||
let label = context.dataset.label || '';
|
||
if (label) {
|
||
label += ': ';
|
||
}
|
||
if (context.parsed.y !== null) {
|
||
if (selectedMetric === 'temperature') {
|
||
label += context.parsed.y.toFixed(1);
|
||
} else {
|
||
label += context.parsed.y.toFixed(3);
|
||
}
|
||
}
|
||
return label;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
scales: {
|
||
x: {
|
||
title: {
|
||
display: true,
|
||
text: '时间'
|
||
}
|
||
},
|
||
y: {
|
||
title: {
|
||
display: true,
|
||
text: selectedMetric === 'temperature' ? '温度(°C)' : selectedMetric.toUpperCase()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function computeCurrentStddev(data) {
|
||
if (!Array.isArray(data) || data.length === 0) return {stddev: 0, count: 0, min: null, max: null};
|
||
const sidVal = (document.getElementById('sensorSelect') || {}).value || 'all';
|
||
const filterSid = sidVal !== 'all' ? parseInt(sidVal) : null;
|
||
let n = 0, mean = 0, M2 = 0, min = Infinity, max = -Infinity;
|
||
for (const row of data) {
|
||
if (filterSid !== null && row.sensor_id !== filterSid) continue;
|
||
const v = row[selectedMetric];
|
||
if (typeof v !== 'number' || isNaN(v)) continue;
|
||
n += 1;
|
||
if (v < min) min = v;
|
||
if (v > max) max = v;
|
||
const delta = v - mean;
|
||
mean += delta / n;
|
||
const delta2 = v - mean;
|
||
M2 += delta * delta2;
|
||
}
|
||
if (n < 2) return {stddev: 0, count: n, min: n ? min : null, max: n ? max : null};
|
||
const variance = M2 / (n - 1);
|
||
return {stddev: Math.sqrt(variance), count: n, min, max};
|
||
}
|
||
|
||
|
||
function prepareChartData(data) {
|
||
if (!Array.isArray(data) || data.length === 0) {
|
||
return { labels: [], datasets: [] };
|
||
}
|
||
|
||
|
||
const sortedData = [...data].sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
||
const sensorIDs = [...new Set(sortedData.map(item => item.sensor_id))];
|
||
|
||
|
||
let labels = [];
|
||
if (sensorIDs.length > 0) {
|
||
const firstSensorData = sortedData.filter(item => item.sensor_id === sensorIDs[0]);
|
||
labels = firstSensorData.map(item => {
|
||
const date = new Date(item.timestamp);
|
||
return date.toLocaleString('zh-CN', {
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
});
|
||
});
|
||
}
|
||
|
||
const colors = [
|
||
'rgb(75, 192, 192)',
|
||
'rgb(255, 99, 132)',
|
||
'rgb(54, 162, 235)',
|
||
'rgb(255, 205, 86)',
|
||
'rgb(153, 102, 255)',
|
||
'rgb(255, 159, 64)'
|
||
];
|
||
|
||
const datasets = sensorIDs.map((sid, idx) => {
|
||
const sensorData = sortedData.filter(item => item.sensor_id === sid);
|
||
const color = colors[idx % colors.length];
|
||
return {
|
||
label: `探头 ${sid}`,
|
||
data: sensorData.map(item => item[selectedMetric]),
|
||
fill: false,
|
||
borderColor: color,
|
||
tension: 0.1
|
||
};
|
||
});
|
||
|
||
return { labels, datasets };
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|