天气数据可视化

<!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="https://cdn.jsdelivr.net/npm/vue@3.2.31/dist/vue.global.js"></script>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            text-align: center;
            margin-bottom: 30px;
        }
        .chart-container {
            margin-bottom: 40px;
            padding: 20px;
            border: 1px solid #eee;
            border-radius: 5px;
            background-color: white;
        }
        .chart-title {
            margin-top: 0;
            color: #444;
            border-bottom: 1px solid #eee;
            padding-bottom: 10px;
        }
        .controls {
            display: flex;
            justify-content: space-between;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        select, button {
            padding: 8px 15px;
            border-radius: 4px;
            border: 1px solid #ddd;
            background-color: white;
            cursor: pointer;
        }
        button {
            background-color: #4CAF50;
            color: white;
            border: none;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #45a049;
        }
        .link {
            text-align: center;
            margin-top: 20px;
            color: #666;
        }
        .link a {
            color: #4CAF50;
            text-decoration: none;
        }
        .link a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
    <div id="app" class="container">
        <h1>天气数据可视化</h1>
        
        <div class="controls">
            <div>
                <label for="city">选择城市: </label>
                <select id="city" v-model="selectedCity" @change="fetchWeatherData">
                    <option v-for="city in cities" :value="city">{{ city }}</option>
                </select>
            </div>
            <div>
                <label for="days">显示天数: </label>
                <select id="days" v-model="selectedDays" @change="fetchWeatherData">
                    <option v-for="day in [3,5,7]" :value="day">{{ day }}天</option>
                </select>
            </div>
            <button @click="fetchWeatherData">刷新数据</button>
        </div>
        
        <div class="chart-container">
            <h2 class="chart-title">温度柱状图</h2>
            <div id="bar-chart"></div>
        </div>
        
        <div class="chart-container">
            <h2 class="chart-title">温度变化折线图</h2>
            <div id="line-chart"></div>
        </div>
        
        <div class="chart-container">
            <h2 class="chart-title">天气状况饼图</h2>
            <div id="pie-chart"></div>
        </div>
        

    </div>

    <script>
        const { createApp, ref, onMounted } = Vue;
        
        createApp({
            setup() {
                const selectedCity = ref('北京');
                const selectedDays = ref(5);
                const cities = ['北京', '上海', '广州', '深圳', '成都', '杭州', '武汉'];
                const weatherData = ref([]);
                
                // 模拟从FastAPI获取数据
                const fetchWeatherData = async () => {
                    // 这里应该是实际的API调用,例如:
                    // const response = await fetch(`/api/weather?city=${selectedCity.value}&days=${selectedDays.value}`);
                    // weatherData.value = await response.json();
                    
                    // 模拟API响应
                    const mockData = generateMockWeatherData(selectedCity.value, selectedDays.value);
                    weatherData.value = mockData;
                    
                    // 更新图表
                    updateCharts();
                };
                
                // 生成模拟天气数据
                const generateMockWeatherData = (city, days) => {
                    const conditions = ['晴天', '多云', '小雨', '阴天', '雷阵雨'];
                    const data = [];
                    
                    for (let i = 0; i < days; i++) {
                        const date = new Date();
                        date.setDate(date.getDate() + i);
                        
                        const condition = conditions[Math.floor(Math.random() * conditions.length)];
                        const maxTemp = Math.round(20 + Math.random() * 15);
                        const minTemp = Math.round(maxTemp - 5 - Math.random() * 5);
                        
                        data.push({
                            date: date.toLocaleDateString(),
                            condition,
                            maxTemp,
                            minTemp,
                            precipitation: Math.round(Math.random() * 10),
                            humidity: Math.round(50 + Math.random() * 40)
                        });
                    }
                    
                    return data;
                };
                
                // 更新所有图表
                const updateCharts = () => {
                    if (weatherData.value.length === 0) return;
                    
                    createBarChart();
                    createLineChart();
                    createPieChart();
                };
                
                // 创建柱状图
                const createBarChart = () => {
                    d3.select('#bar-chart').selectAll('*').remove();
                    
                    const margin = {top: 20, right: 30, bottom: 40, left: 40};
                    const width = 800 - margin.left - margin.right;
                    const height = 400 - margin.top - margin.bottom;
                    
                    const svg = d3.select('#bar-chart')
                        .append('svg')
                        .attr('width', width + margin.left + margin.right)
                        .attr('height', height + margin.top + margin.bottom)
                        .append('g')
                        .attr('transform', `translate(${margin.left},${margin.top})`);
                    
                    const x = d3.scaleBand()
                        .domain(weatherData.value.map(d => d.date))
                        .range([0, width])
                        .padding(0.2);
                    
                    const y = d3.scaleLinear()
                        .domain([0, d3.max(weatherData.value, d => d.maxTemp) + 5])
                        .range([height, 0]);
                    
                    // 添加最高温度柱状图
                    svg.selectAll('.max-bar')
                        .data(weatherData.value)
                        .enter()
                        .append('rect')
                        .attr('class', 'max-bar')
                        .attr('x', d => x(d.date))
                        .attr('y', d => y(d.maxTemp))
                        .attr('width', x.bandwidth())
                        .attr('height', d => height - y(d.maxTemp))
                        .attr('fill', '#ff7f0e');
                    
                    // 添加最低温度柱状图
                    svg.selectAll('.min-bar')
                        .data(weatherData.value)
                        .enter()
                        .append('rect')
                        .attr('class', 'min-bar')
                        .attr('x', d => x(d.date))
                        .attr('y', d => y(d.minTemp))
                        .attr('width', x.bandwidth())
                        .attr('height', d => height - y(d.minTemp))
                        .attr('fill', '#1f77b4');
                    
                    // 添加X轴
                    svg.append('g')
                        .attr('transform', `translate(0,${height})`)
                        .call(d3.axisBottom(x));
                    
                    // 添加Y轴
                    svg.append('g')
                        .call(d3.axisLeft(y));
                    
                    // 添加图例
                    const legend = svg.append('g')
                        .attr('transform', `translate(${width - 100}, 0)`);
                    
                    legend.append('rect')
                        .attr('x', 0)
                        .attr('y', 0)
                        .attr('width', 15)
                        .attr('height', 15)
                        .attr('fill', '#ff7f0e');
                    
                    legend.append('text')
                        .attr('x', 20)
                        .attr('y', 12)
                        .text('最高温度')
                        .style('font-size', '12px');
                    
                    legend.append('rect')
                        .attr('x', 0)
                        .attr('y', 20)
                        .attr('width', 15)
                        .attr('height', 15)
                        .attr('fill', '#1f77b4');
                    
                    legend.append('text')
                        .attr('x', 20)
                        .attr('y', 32)
                        .text('最低温度')
                        .style('font-size', '12px');
                    
                    // 添加标题
                    svg.append('text')
                        .attr('x', width / 2)
                        .attr('y', -5)
                        .attr('text-anchor', 'middle')
                        .style('font-size', '14px')
                        .text(`${selectedCity.value}未来${selectedDays.value}天温度变化`);
                };
                
                // 创建折线图
                const createLineChart = () => {
                    d3.select('#line-chart').selectAll('*').remove();
                    
                    const margin = {top: 20, right: 30, bottom: 40, left: 40};
                    const width = 800 - margin.left - margin.right;
                    const height = 400 - margin.top - margin.bottom;
                    
                    const svg = d3.select('#line-chart')
                        .append('svg')
                        .attr('width', width + margin.left + margin.right)
                        .attr('height', height + margin.top + margin.bottom)
                        .append('g')
                        .attr('transform', `translate(${margin.left},${margin.top})`);
                    
                    const x = d3.scaleBand()
                        .domain(weatherData.value.map(d => d.date))
                        .range([0, width])
                        .padding(0.2);
                    
                    const y = d3.scaleLinear()
                        .domain([0, d3.max(weatherData.value, d => d.maxTemp) + 5])
                        .range([height, 0]);
                    
                    // 定义折线生成器
                    const line = d3.line()
                        .x(d => x(d.date) + x.bandwidth() / 2)
                        .y(d => y(d.maxTemp));
                    
                    // 添加最高温度折线
                    svg.append('path')
                        .datum(weatherData.value)
                        .attr('fill', 'none')
                        .attr('stroke', '#ff7f0e')
                        .attr('stroke-width', 2)
                        .attr('d', line);
                    
                    // 添加最低温度折线
                    const minLine = d3.line()
                        .x(d => x(d.date) + x.bandwidth() / 2)
                        .y(d => y(d.minTemp));
                    
                    svg.append('path')
                        .datum(weatherData.value)
                        .attr('fill', 'none')
                        .attr('stroke', '#1f77b4')
                        .attr('stroke-width', 2)
                        .attr('d', minLine);
                    
                    // 添加X轴
                    svg.append('g')
                        .attr('transform', `translate(0,${height})`)
                        .call(d3.axisBottom(x));
                    
                    // 添加Y轴
                    svg.append('g')
                        .call(d3.axisLeft(y));
                    
                    // 添加图例
                    const legend = svg.append('g')
                        .attr('transform', `translate(${width - 100}, 0)`);
                    
                    legend.append('line')
                        .attr('x1', 0)
                        .attr('y1', 5)
                        .attr('x2', 15)
                        .attr('y2', 5)
                        .attr('stroke', '#ff7f0e')
                        .attr('stroke-width', 2);
                    
                    legend.append('text')
                        .attr('x', 20)
                        .attr('y', 8)
                        .text('最高温度')
                        .style('font-size', '12px');
                    
                    legend.append('line')
                        .attr('x1', 0)
                        .attr('y1', 25)
                        .attr('x2', 15)
                        .attr('y2', 25)
                        .attr('stroke', '#1f77b4')
                        .attr('stroke-width', 2);
                    
                    legend.append('text')
                        .attr('x', 20)
                        .attr('y', 28)
                        .text('最低温度')
                        .style('font-size', '12px');
                    
                    // 添加标题
                    svg.append('text')
                        .attr('x', width / 2)
                        .attr('y', -5)
                        .attr('text-anchor', 'middle')
                        .style('font-size', '14px')
                        .text(`${selectedCity.value}未来${selectedDays.value}天温度趋势`);
                };
                
                // 创建饼图
                const createPieChart = () => {
                    d3.select('#pie-chart').selectAll('*').remove();
                    
                    const margin = {top: 20, right: 30, bottom: 40, left: 30};
                    const width = 600 - margin.left - margin.right;
                    const height = 400 - margin.top - margin.bottom;
                    const radius = Math.min(width, height) / 2;
                    
                    const svg = d3.select('#pie-chart')
                        .append('svg')
                        .attr('width', width + margin.left + margin.right)
                        .attr('height', height + margin.top + margin.bottom)
                        .append('g')
                        .attr('transform', `translate(${width / 2 + margin.left},${height / 2 + margin.top})`);
                    
                    // 统计天气状况
                    const conditionCounts = {};
                    weatherData.value.forEach(day => {
                        conditionCounts[day.condition] = (conditionCounts[day.condition] || 0) + 1;
                    });
                    
                    const data = Object.keys(conditionCounts).map(key => ({
                        condition: key,
                        count: conditionCounts[key]
                    }));
                    
                    const color = d3.scaleOrdinal()
                        .domain(data.map(d => d.condition))
                        .range(d3.schemeCategory10);
                    
                    const pie = d3.pie()
                        .value(d => d.count);
                    
                    const arc = d3.arc()
                        .innerRadius(0)
                        .outerRadius(radius);
                    
                    const arcs = svg.selectAll('arc')
                        .data(pie(data))
                        .enter()
                        .append('g')
                        .attr('class', 'arc');
                    
                    arcs.append('path')
                        .attr('d', arc)
                        .attr('fill', d => color(d.data.condition))
                        .attr('stroke', 'white')
                        .style('stroke-width', '2px');
                    
                    // 添加标签
                    arcs.append('text')
                        .attr('transform', d => `translate(${arc.centroid(d)})`)
                        .attr('text-anchor', 'middle')
                        .text(d => d.data.condition)
                        .style('font-size', '12px');
                    
                    // 添加图例
                    const legend = svg.append('g')
                        .attr('transform', `translate(${radius + 20}, -${radius / 2})`);
                    
                    data.forEach((item, i) => {
                        const legendItem = legend.append('g')
                            .attr('transform', `translate(0, ${i * 20})`);
                        
                        legendItem.append('rect')
                            .attr('width', 15)
                            .attr('height', 15)
                            .attr('fill', color(item.condition));
                        
                        legendItem.append('text')
                            .attr('x', 20)
                            .attr('y', 12)
                            .text(`${item.condition} (${item.count}天)`)
                            .style('font-size', '12px');
                    });
                    
                    // 添加标题
                    svg.append('text')
                        .attr('x', 0)
                        .attr('y', -radius - 10)
                        .attr('text-anchor', 'middle')
                        .style('font-size', '14px')
                        .text(`${selectedCity.value}未来${selectedDays.value}天天气状况分布`);
                };
                
                // 初始化
                onMounted(() => {
                    fetchWeatherData();
                });
                
                return {
                    selectedCity,
                    selectedDays,
                    cities,
                    weatherData,
                    fetchWeatherData
                };
            }
        }).mount('#app');
    </script>
</body>
</html>

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值