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; } 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; }); 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) => { 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) - 实测', seriesKey: 'temp_actual', data: historyTemperatures, borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.1)', yAxisID: 'y-temperature', tension: 0.4, spanGaps: false }, { label: '湿度 (%) - 实测', seriesKey: 'hum_actual', 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) - 实测', seriesKey: 'pressure_actual', 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) - 实测', seriesKey: 'wind_actual', 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) - 实测', seriesKey: 'rain_actual', data: historyRainfalls, type: 'bar', backgroundColor: 'rgba(54, 162, 235, 0.6)', borderColor: 'rgb(54, 162, 235)', yAxisID: 'y-rainfall' }, { label: '累计雨量 (mm) - 实测', seriesKey: 'rain_total', 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) { datasets.push( { label: '雨量 (mm) - 预报 (+1h)', seriesKey: 'rain_fcst_h1', data: forecastRainfallsH1, type: 'bar', backgroundColor: 'rgba(255, 99, 71, 0.55)', borderColor: 'rgb(255, 99, 71)', yAxisID: 'y-rainfall' }, { label: '雨量 (mm) - 预报 (+2h)', seriesKey: 'rain_fcst_h2', data: forecastRainfallsH2, type: 'bar', backgroundColor: 'rgba(255, 205, 86, 0.55)', borderColor: 'rgb(255, 205, 86)', yAxisID: 'y-rainfall' }, { label: '雨量 (mm) - 预报 (+3h)', seriesKey: 'rain_fcst_h3', data: forecastRainfallsH3, type: 'bar', backgroundColor: 'rgba(76, 175, 80, 0.55)', borderColor: 'rgb(76, 175, 80)', yAxisID: 'y-rainfall' } ); datasets.push( { label: '温度 (°C) - 预报', seriesKey: 'temp_fcst', 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, hidden: true }, { label: '湿度 (%) - 预报', seriesKey: 'hum_fcst', 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) - 预报', seriesKey: 'pressure_fcst', 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) - 预报', seriesKey: 'wind_fcst', 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'); const chartConfig = { 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: 'right', 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: 'left', title: { display: true, text: '大气压 (hPa)' }, grid: { drawOnChartArea: false } }, 'y-wind': { type: 'linear', display: true, position: 'left', 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 } } } }; this.chart = new Chart(ctx, chartConfig); const mode = document.getElementById('legendMode')?.value || 'combo_standard'; this.applyLegendMode(mode); } }; WeatherChart.applyLegendMode = function(mode) { if (!this.chart) return; // 设置数据集可见性 const map = new Map(); this.chart.data.datasets.forEach(ds => { if (ds.seriesKey) map.set(ds.seriesKey, ds); }); const setVisible = (keys) => { const allKeys = ['temp_actual','hum_actual','rain_actual','rain_total','temp_fcst','hum_fcst','pressure_actual','pressure_fcst','wind_actual','wind_fcst','rain_fcst_h1','rain_fcst_h2','rain_fcst_h3']; allKeys.forEach(k => { if (map.has(k)) map.get(k).hidden = true; }); keys.forEach(k => { if (map.has(k)) map.get(k).hidden = false; }); }; switch (mode) { case 'combo_compact': setVisible(['temp_actual','hum_actual','rain_actual','rain_fcst_h1']); break; case 'verify_all': setVisible(['temp_actual','temp_fcst','hum_actual','hum_fcst','pressure_actual','pressure_fcst','wind_actual','wind_fcst','rain_actual','rain_fcst_h1','rain_fcst_h2','rain_fcst_h3']); break; case 'temp_compare': setVisible(['temp_actual','temp_fcst']); break; case 'hum_compare': setVisible(['hum_actual','hum_fcst']); break; case 'rain_all': setVisible(['rain_actual','rain_fcst_h1','rain_fcst_h2','rain_fcst_h3']); break; case 'pressure_compare': setVisible(['pressure_actual','pressure_fcst']); break; case 'wind_compare': setVisible(['wind_actual','wind_fcst']); break; case 'combo_standard': default: setVisible(['temp_actual','hum_actual','rain_actual','rain_fcst_h1','rain_fcst_h2','rain_fcst_h3']); break; } this.chart.update(); }; WeatherChart.updateAxesVisibility = function() { if (!this.chart) return; // 检查每个轴是否有可见的数据集 const hasVisibleDatasets = (axisId) => { return this.chart.data.datasets.some(ds => !ds.hidden && ds.yAxisID === axisId); }; // 更新每个轴的显示状态 Object.entries(this.chart.options.scales).forEach(([scaleId, scale]) => { const shouldDisplay = hasVisibleDatasets(scaleId); if (scale.display !== shouldDisplay) { scale.display = shouldDisplay; } }); }; window.WeatherChart = WeatherChart;