weather-station/static/js/weather-chart.js
2025-08-29 20:23:13 +08:00

167 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 图表相关功能
const WeatherChart = {
chart: null,
// 显示图表
display(historyData = [], forecastData = []) {
// 确保数据是数组
historyData = Array.isArray(historyData) ? historyData : [];
forecastData = Array.isArray(forecastData) ? forecastData : [];
// 如果没有任何数据,则不绘制图表
if (historyData.length === 0 && forecastData.length === 0) {
return;
}
// 合并历史数据和预报数据的时间轴(按 date_time 对齐)
const allLabels = [...new Set([
...historyData.map(item => item.date_time),
...forecastData.map(item => item.date_time)
])].sort();
// 历史数据
const historyTemperatures = allLabels.map(label => {
const item = historyData.find(d => d.date_time === label);
return item ? item.temperature : null;
});
const historyHumidities = allLabels.map(label => {
const item = historyData.find(d => d.date_time === label);
return item ? item.humidity : null;
});
const historyRainfalls = allLabels.map(label => {
const item = historyData.find(d => d.date_time === label);
return item ? item.rainfall : null;
});
const historyRainTotals = allLabels.map(label => {
const item = historyData.find(d => d.date_time === label);
return item && item.rain_total !== undefined ? item.rain_total : null;
});
// 预报:按同一 forecast_time 的不同 lead_hours 组织(仅用 0..3h
const byTime = new Map();
forecastData.forEach(fp => {
if (!byTime.has(fp.date_time)) byTime.set(fp.date_time, {});
const bucket = byTime.get(fp.date_time);
const h = typeof fp.lead_hours === 'number' ? fp.lead_hours : null;
if (h !== null && h >= 0 && h <= 3) {
bucket[h] = fp;
}
});
const getRainAtLead = (label, lead) => {
const b = byTime.get(label);
if (!b || !b[lead]) return null;
return b[lead].rainfall != null ? b[lead].rainfall : null;
};
const getTempAtLead0 = (label) => {
const b = byTime.get(label);
if (!b || !b[0]) return null;
return b[0].temperature != null ? b[0].temperature : null;
};
const forecastRainfallsH0 = allLabels.map(label => getRainAtLead(label, 0));
const forecastRainfallsH1 = allLabels.map(label => getRainAtLead(label, 1));
const forecastRainfallsH2 = allLabels.map(label => getRainAtLead(label, 2));
const forecastRainfallsH3 = allLabels.map(label => getRainAtLead(label, 3));
const pickNearestTemp = (label) => {
// 近似优先级0h > 1h > 2h > 3h
const b = byTime.get(label);
if (!b) return null;
if (b[0] && b[0].temperature != null) return b[0].temperature;
if (b[1] && b[1].temperature != null) return b[1].temperature;
if (b[2] && b[2].temperature != null) return b[2].temperature;
if (b[3] && b[3].temperature != null) return b[3].temperature;
return null;
};
const forecastTemperaturesNearest = allLabels.map(label => pickNearestTemp(label));
// 销毁旧图表
if (this.chart) this.chart.destroy();
// 数据集
const datasets = [
{
label: '温度 (°C) - 实测',
data: historyTemperatures,
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
yAxisID: 'y-temperature',
tension: 0.4,
spanGaps: false
},
{
label: '湿度 (%) - 实测',
data: historyHumidities,
borderColor: 'rgb(54, 162, 235)',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
yAxisID: 'y-humidity',
tension: 0.4,
hidden: true,
spanGaps: false
},
{
label: '雨量 (mm) - 实测',
data: historyRainfalls,
type: 'bar',
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgb(54, 162, 235)',
yAxisID: 'y-rainfall'
},
{
label: '累计雨量 (mm) - 实测',
data: historyRainTotals,
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
yAxisID: 'y-rainfall',
tension: 0.2,
spanGaps: false,
pointRadius: 0,
hidden: true
}
];
if (forecastData.length > 0) {
// 雨量 仅显示 -1h/-2h/-3h
datasets.push(
{ label: '雨量 (mm) - 预报 (-1h)', data: forecastRainfallsH1, type: 'bar', backgroundColor: 'rgba(255, 99, 71, 0.55)', borderColor: 'rgb(255, 99, 71)', yAxisID: 'y-rainfall' },
{ label: '雨量 (mm) - 预报 (-2h)', data: forecastRainfallsH2, type: 'bar', backgroundColor: 'rgba(255, 205, 86, 0.55)', borderColor: 'rgb(255, 205, 86)', yAxisID: 'y-rainfall' },
{ label: '雨量 (mm) - 预报 (-3h)', data: forecastRainfallsH3, type: 'bar', backgroundColor: 'rgba(76, 175, 80, 0.55)', borderColor: 'rgb(76, 175, 80)', yAxisID: 'y-rainfall' }
);
// 温度 取最接近的预报0h>1h>2h>3h
datasets.push({
label: '温度 (°C) - 预报',
data: forecastTemperaturesNearest,
borderColor: 'rgb(255, 159, 64)',
backgroundColor: 'rgba(255, 159, 64, 0.1)',
borderDash: [5, 5],
yAxisID: 'y-temperature',
tension: 0.4,
spanGaps: false
});
}
// 创建组合图表
const ctx = document.getElementById('combinedChart').getContext('2d');
this.chart = new Chart(ctx, {
type: 'line',
data: {
labels: allLabels,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
scales: {
'y-temperature': { type: 'linear', display: true, position: 'left', title: { display: true, text: '温度 (°C)' } },
'y-humidity': { type: 'linear', display: true, position: 'right', title: { display: true, text: '湿度 (%)' }, grid: { drawOnChartArea: false }, min: 0, max: 100 },
'y-rainfall': { type: 'linear', display: true, position: 'right', title: { display: true, text: '雨量 (mm)' }, grid: { drawOnChartArea: false }, beginAtZero: true }
}
}
});
}
};
// 导出图表对象
window.WeatherChart = WeatherChart;