// 图表相关功能 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 }, 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-rainfall': { type: 'linear', display: true, position: 'right', title: { display: true, text: '雨量 (mm)' }, grid: { drawOnChartArea: false }, beginAtZero: true } } } }); } }; // 导出图表对象 window.WeatherChart = WeatherChart;