280 lines
8.4 KiB
JavaScript
280 lines
8.4 KiB
JavaScript
// 图表相关功能
|
||
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 historyPressures = allLabels.map(label => {
|
||
const item = historyData.find(d => d.date_time === label);
|
||
return item ? item.pressure : null;
|
||
});
|
||
const historyWindSpeeds = allLabels.map(label => {
|
||
const item = historyData.find(d => d.date_time === label);
|
||
return item ? item.wind_speed : 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 pickNearest = (label, field) => {
|
||
// 近似优先级:0h > 1h > 2h > 3h
|
||
const b = byTime.get(label);
|
||
if (!b) return null;
|
||
if (b[0] && b[0][field] != null) return b[0][field];
|
||
if (b[1] && b[1][field] != null) return b[1][field];
|
||
if (b[2] && b[2][field] != null) return b[2][field];
|
||
if (b[3] && b[3][field] != null) return b[3][field];
|
||
return null;
|
||
};
|
||
const forecastTemperaturesNearest = allLabels.map(label => pickNearest(label, 'temperature'));
|
||
const forecastHumiditiesNearest = allLabels.map(label => pickNearest(label, 'humidity'));
|
||
const forecastPressuresNearest = allLabels.map(label => pickNearest(label, 'pressure'));
|
||
const forecastWindSpeedsNearest = allLabels.map(label => pickNearest(label, 'wind_speed'));
|
||
|
||
// 销毁旧图表
|
||
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: '大气压 (hPa) - 实测',
|
||
data: historyPressures,
|
||
borderColor: 'rgb(153, 102, 255)',
|
||
backgroundColor: 'rgba(153, 102, 255, 0.1)',
|
||
yAxisID: 'y-pressure',
|
||
tension: 0.4,
|
||
hidden: true,
|
||
spanGaps: false
|
||
},
|
||
{
|
||
label: '风速 (m/s) - 实测',
|
||
data: historyWindSpeeds,
|
||
borderColor: 'rgb(75, 192, 192)',
|
||
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
||
yAxisID: 'y-wind',
|
||
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
|
||
},
|
||
{
|
||
label: '湿度 (%) - 预报',
|
||
data: forecastHumiditiesNearest,
|
||
borderColor: 'rgb(54, 162, 235)',
|
||
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
||
borderDash: [5, 5],
|
||
yAxisID: 'y-humidity',
|
||
tension: 0.4,
|
||
hidden: true,
|
||
spanGaps: false
|
||
},
|
||
{
|
||
label: '大气压 (hPa) - 预报',
|
||
data: forecastPressuresNearest,
|
||
borderColor: 'rgb(153, 102, 255)',
|
||
backgroundColor: 'rgba(153, 102, 255, 0.1)',
|
||
borderDash: [5, 5],
|
||
yAxisID: 'y-pressure',
|
||
tension: 0.4,
|
||
hidden: true,
|
||
spanGaps: false
|
||
},
|
||
{
|
||
label: '风速 (m/s) - 预报',
|
||
data: forecastWindSpeedsNearest,
|
||
borderColor: 'rgb(75, 192, 192)',
|
||
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
||
borderDash: [5, 5],
|
||
yAxisID: 'y-wind',
|
||
tension: 0.4,
|
||
hidden: true,
|
||
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 },
|
||
layout: { padding: { top: 12, right: 16, bottom: 12, left: 16 } },
|
||
plugins: {
|
||
legend: {
|
||
display: true,
|
||
position: 'top',
|
||
align: 'center',
|
||
labels: {
|
||
padding: 16
|
||
}
|
||
}
|
||
},
|
||
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-pressure': {
|
||
type: 'linear',
|
||
display: true,
|
||
position: 'right',
|
||
title: { display: true, text: '大气压 (hPa)' },
|
||
grid: { drawOnChartArea: false }
|
||
},
|
||
'y-wind': {
|
||
type: 'linear',
|
||
display: true,
|
||
position: 'right',
|
||
title: { display: true, text: '风速 (m/s)' },
|
||
grid: { drawOnChartArea: false },
|
||
beginAtZero: true
|
||
},
|
||
'y-rainfall': {
|
||
type: 'linear',
|
||
display: true,
|
||
position: 'right',
|
||
title: { display: true, text: '雨量 (mm)' },
|
||
grid: { drawOnChartArea: false },
|
||
beginAtZero: true
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
};
|
||
|
||
// 导出图表对象
|
||
window.WeatherChart = WeatherChart;
|