vue+d3js+fastapi實(shí)現(xiàn)天氣柱狀圖折線圖餅圖的示例
說(shuō)明:
vue+d3js+fastapi實(shí)現(xiàn)天氣柱狀圖折線圖餅圖
效果圖:

step0:postman
1. 生成天氣數(shù)據(jù)(POST請(qǐng)求):
URL: http://localhost:8000/generate-data/?year=2024&month=3&seed=42
方法: POST
Headers: Content-Type: application/json
成功響應(yīng)示例:
{
"status": "success",
"message": "成功生成31條天氣數(shù)據(jù)",
"year": 2024,
"month": 3
}2. 查詢天氣數(shù)據(jù)(GET請(qǐng)求):
URL: http://localhost:8000/weather-data/?year=2024&month=4
方法: GET
成功響應(yīng)示例:
{
"status": "success",
"count": 31,
"year": 2024,
"month": 3,
"data": [
{
"record_date": "2024-03-01",
"temperature": 16.4,
"humidity": 72,
"precipitation": 0.0,
"wind_speed": 7.2,
"weather_condition": "Cloudy"
},
{
"record_date": "2024-03-31",
"temperature": 17.3,
"humidity": 62,
"precipitation": 3.8,
"wind_speed": 1.4,
"weather_condition": "Rain"
}
]
}
step1:sql
CREATE TABLE weather_data (
id INT AUTO_INCREMENT PRIMARY KEY,
record_date DATE NOT NULL,
temperature DECIMAL(4,1) NOT NULL, -- 格式:-99.9 到 99.9
humidity TINYINT UNSIGNED NOT NULL, -- 范圍:0-100
precipitation DECIMAL(5,1) NOT NULL, -- 最大999.9mm
wind_speed DECIMAL(4,1) NOT NULL, -- 最大99.9m/s
weather_condition VARCHAR(50) NOT NULL, -- 修改列名
INDEX (record_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
select *from weather_data;
step2:python test
C:\Users\wangrusheng\PycharmProjects\FastAPIProject1\hello.py
import random
from datetime import date
from decimal import Decimal
import calendar
import pymysql.cursors
import json
# 數(shù)據(jù)庫(kù)配置(根據(jù)實(shí)際情況修改)
DB_CONFIG = {
'host': 'localhost',
'user': 'root',
'password': '123456',
'db': 'db_school',
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
def generate_temperature(rng, min_temp=10.0, max_temp=20.0):
"""生成溫度數(shù)據(jù)(均勻分布)"""
temp = rng.uniform(min_temp, max_temp)
return round(temp, 1)
def generate_humidity(rng):
"""生成濕度數(shù)據(jù)(正態(tài)分布)"""
humidity = rng.gauss(60, 15)
humidity = max(0, min(humidity, 100))
return int(round(humidity))
def generate_precipitation(rng):
"""生成降水量數(shù)據(jù)(20%概率下雨)"""
if rng.random() < 0.2:
amount = rng.expovariate(1 / 5.0) # 平均5mm
amount = max(0.1, min(amount, 30.0))
return round(amount, 1)
return 0.0
def generate_wind_speed(rng):
"""生成風(fēng)速數(shù)據(jù)(伽馬分布)"""
speed = rng.gammavariate(2, 2)
speed = max(0.0, min(speed, 20.0))
return round(speed, 1)
def get_weather_condition(temperature, precipitation, humidity, rng):
"""根據(jù)天氣參數(shù)判斷天氣狀況"""
if precipitation > 0:
return 'Snow' if temperature < 3.0 else 'Rain'
if humidity >= 70:
return 'Cloudy'
if humidity <= 30:
return 'Sunny'
return rng.choice(['Partly Cloudy', 'Mostly Cloudy'])
def generate_monthly_weather_data(year, month, rng=None):
"""生成整月天氣數(shù)據(jù)"""
if rng is None:
rng = random.Random()
_, num_days = calendar.monthrange(year, month)
data = []
for day in range(1, num_days + 1):
record_date = date(year, month, day)
temperature = generate_temperature(rng)
humidity = generate_humidity(rng)
precipitation = generate_precipitation(rng)
wind_speed = generate_wind_speed(rng)
condition = get_weather_condition(
temperature, precipitation, humidity, rng
)
data.append({
'record_date': record_date,
'temperature': temperature,
'humidity': humidity,
'precipitation': precipitation,
'wind_speed': wind_speed,
'weather_condition': condition
})
return data
def insert_weather_data(data):
"""批量插入天氣數(shù)據(jù)到數(shù)據(jù)庫(kù)"""
connection = pymysql.connect(**DB_CONFIG)
try:
with connection.cursor() as cursor:
sql = """
INSERT INTO weather_data
(record_date, temperature, humidity, precipitation, wind_speed, weather_condition)
VALUES (%s, %s, %s, %s, %s, %s)
"""
params = [
(
d['record_date'],
d['temperature'],
d['humidity'],
d['precipitation'],
d['wind_speed'],
d['weather_condition']
)
for d in data
]
cursor.executemany(sql, params)
connection.commit()
return len(data)
except Exception as e:
connection.rollback()
raise e
finally:
connection.close()
def get_weather_data(year: int, month: int) -> list:
"""從數(shù)據(jù)庫(kù)獲取指定年月的天氣數(shù)據(jù)并轉(zhuǎn)換為JSON兼容格式"""
connection = pymysql.connect(**DB_CONFIG)
try:
with connection.cursor() as cursor:
sql = """
SELECT record_date, temperature, humidity,
precipitation, wind_speed, weather_condition
FROM weather_data
WHERE YEAR(record_date) = %s AND MONTH(record_date) = %s
ORDER BY record_date
"""
cursor.execute(sql, (year, month))
results = cursor.fetchall()
# 轉(zhuǎn)換日期和數(shù)值類(lèi)型
for record in results:
record['record_date'] = record['record_date'].isoformat()
# 處理Decimal類(lèi)型(如果存在)
for key in ['temperature', 'precipitation', 'wind_speed']:
if isinstance(record[key], Decimal):
record[key] = float(record[key])
return results
finally:
connection.close()
if __name__ == '__main__':
# 示例:生成并插入2024年4月的天氣數(shù)據(jù)
year = 2024
month = 4
# 創(chuàng)建帶種子的隨機(jī)生成器(保證結(jié)果可復(fù)現(xiàn))
rng = random.Random(42)
try:
# 生成模擬數(shù)據(jù)
weather_data = generate_monthly_weather_data(year, month, rng)
# 插入數(shù)據(jù)庫(kù)
# inserted_count = insert_weather_data(weather_data)
# print(f"成功插入{inserted_count}條天氣數(shù)據(jù)")
# 獲取并打印JSON數(shù)據(jù)
weather_json = get_weather_data(year, month)
print(json.dumps(weather_json, indent=2, ensure_ascii=False))
except Exception as e:
print(f"操作失敗: {str(e)}")
step3:python fastapi
C:\Users\wangrusheng\PycharmProjects\FastAPIProject1\main.py
from fastapi import FastAPI, HTTPException, Query
from datetime import date
from decimal import Decimal
from typing import Optional
import random
import calendar
import pymysql.cursors
import json
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# CORS配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 數(shù)據(jù)庫(kù)配置(根據(jù)實(shí)際情況修改)
DB_CONFIG = {
'host': 'localhost',
'user': 'root',
'password': '123456',
'db': 'db_school',
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
# 以下保持原有函數(shù)定義不變(generate_temperature、generate_humidity等)
# [原有函數(shù)定義區(qū),保持與問(wèn)題中完全相同的函數(shù)實(shí)現(xiàn)]
def generate_temperature(rng, min_temp=10.0, max_temp=20.0):
"""生成溫度數(shù)據(jù)(均勻分布)"""
temp = rng.uniform(min_temp, max_temp)
return round(temp, 1)
def generate_humidity(rng):
"""生成濕度數(shù)據(jù)(正態(tài)分布)"""
humidity = rng.gauss(60, 15)
humidity = max(0, min(humidity, 100))
return int(round(humidity))
def generate_precipitation(rng):
"""生成降水量數(shù)據(jù)(20%概率下雨)"""
if rng.random() < 0.2:
amount = rng.expovariate(1 / 5.0) # 平均5mm
amount = max(0.1, min(amount, 30.0))
return round(amount, 1)
return 0.0
def generate_wind_speed(rng):
"""生成風(fēng)速數(shù)據(jù)(伽馬分布)"""
speed = rng.gammavariate(2, 2)
speed = max(0.0, min(speed, 20.0))
return round(speed, 1)
def get_weather_condition(temperature, precipitation, humidity, rng):
"""根據(jù)天氣參數(shù)判斷天氣狀況"""
if precipitation > 0:
return 'Snow' if temperature < 3.0 else 'Rain'
if humidity >= 70:
return 'Cloudy'
if humidity <= 30:
return 'Sunny'
return rng.choice(['Partly Cloudy', 'Mostly Cloudy'])
def generate_monthly_weather_data(year, month, rng=None):
"""生成整月天氣數(shù)據(jù)"""
if rng is None:
rng = random.Random()
_, num_days = calendar.monthrange(year, month)
data = []
for day in range(1, num_days + 1):
record_date = date(year, month, day)
temperature = generate_temperature(rng)
humidity = generate_humidity(rng)
precipitation = generate_precipitation(rng)
wind_speed = generate_wind_speed(rng)
condition = get_weather_condition(
temperature, precipitation, humidity, rng
)
data.append({
'record_date': record_date,
'temperature': temperature,
'humidity': humidity,
'precipitation': precipitation,
'wind_speed': wind_speed,
'weather_condition': condition
})
return data
def insert_weather_data(data):
"""批量插入天氣數(shù)據(jù)到數(shù)據(jù)庫(kù)"""
connection = pymysql.connect(**DB_CONFIG)
try:
with connection.cursor() as cursor:
sql = """
INSERT INTO weather_data
(record_date, temperature, humidity, precipitation, wind_speed, weather_condition)
VALUES (%s, %s, %s, %s, %s, %s)
"""
params = [
(
d['record_date'],
d['temperature'],
d['humidity'],
d['precipitation'],
d['wind_speed'],
d['weather_condition']
)
for d in data
]
cursor.executemany(sql, params)
connection.commit()
return len(data)
except Exception as e:
connection.rollback()
raise e
finally:
connection.close()
def get_weather_data(year: int, month: int) -> list:
"""從數(shù)據(jù)庫(kù)獲取指定年月的天氣數(shù)據(jù)并轉(zhuǎn)換為JSON兼容格式"""
connection = pymysql.connect(**DB_CONFIG)
try:
with connection.cursor() as cursor:
sql = """
SELECT record_date, temperature, humidity,
precipitation, wind_speed, weather_condition
FROM weather_data
WHERE YEAR(record_date) = %s AND MONTH(record_date) = %s
ORDER BY record_date
"""
cursor.execute(sql, (year, month))
results = cursor.fetchall()
# 轉(zhuǎn)換日期和數(shù)值類(lèi)型
for record in results:
record['record_date'] = record['record_date'].isoformat()
# 處理Decimal類(lèi)型(如果存在)
for key in ['temperature', 'precipitation', 'wind_speed']:
if isinstance(record[key], Decimal):
record[key] = float(record[key])
return results
finally:
connection.close()
@app.post("/generate-data/")
async def generate_weather_data(
year: int = Query(..., ge=2000, le=2100, description="年份"),
month: int = Query(..., ge=1, le=12, description="月份"),
seed: Optional[int] = Query(None, description="隨機(jī)種子(可選)")
):
"""生成并插入指定月份的天氣數(shù)據(jù)"""
try:
rng = random.Random(seed) if seed else random.Random()
weather_data = generate_monthly_weather_data(year, month, rng)
inserted_count = insert_weather_data(weather_data)
return {
"status": "success",
"message": f"成功生成{inserted_count}條天氣數(shù)據(jù)",
"year": year,
"month": month
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"數(shù)據(jù)庫(kù)操作失敗: {str(e)}")
@app.get("/weather-data/")
async def get_weather(
year: int = Query(..., ge=2000, le=2100, description="年份"),
month: int = Query(..., ge=1, le=12, description="月份")
):
"""獲取指定月份的天氣數(shù)據(jù)"""
try:
data = get_weather_data(year, month)
return {
"status": "success",
"count": len(data),
"year": year,
"month": month,
"data": data
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"數(shù)據(jù)查詢失敗: {str(e)}")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
step4:vue
C:\Users\wangrusheng\PycharmProjects\untitled3\src\views\Lottery.vue
<template>
<div>
<div class="controls">
<input v-model.number="year" type="number" placeholder="年份">
<input v-model.number="month" type="number" placeholder="月份" min="1" max="12">
<button @click="fetchData">查詢</button>
<button @click="generateData">生成數(shù)據(jù)</button>
</div>
<div class="charts-container">
<div class="chart-box">
<h3>每日溫度柱狀圖</h3>
<div ref="barChart" class="chart"></div>
</div>
<div class="chart-box">
<h3>溫度趨勢(shì)折線圖</h3>
<div ref="lineChart" class="chart"></div>
</div>
<div class="chart-box">
<h3>天氣狀況分布餅圖</h3>
<div ref="pieChart" class="chart"></div>
</div>
</div>
</div>
</template>
<script>
import * as d3 from 'd3';
import axios from 'axios';
export default {
data() {
return {
weatherData: [],
year: null,
month: null
};
},
methods: {
async fetchData() {
if (!this.validateInput()) return;
try {
const response = await axios.get('http://localhost:8000/weather-data/', {
params: { year: this.year, month: this.month }
});
this.weatherData = response.data.data;
this.redrawCharts();
} catch (error) {
this.handleError(error, '查詢');
}
},
async generateData() {
if (!this.validateInput()) return;
try {
const response = await axios.post('http://localhost:8000/generate-data/', null, {
params: { year: this.year, month: this.month, seed: 42 },
headers: { 'Content-Type': 'application/json' }
});
alert(`生成成功:${response.data.message}`);
await this.fetchData();
} catch (error) {
this.handleError(error, '生成');
}
},
validateInput() {
if (!this.year || !this.month) {
alert('請(qǐng)?zhí)顚?xiě)年份和月份');
return false;
}
if (this.month < 1 || this.month > 12) {
alert('月份必須為1-12');
return false;
}
return true;
},
handleError(error, operation) {
console.error(`${operation}失敗:`, error);
alert(`${operation}失敗,請(qǐng)檢查控制臺(tái)`);
},
redrawCharts() {
this.clearCharts();
this.drawBarChart();
this.drawLineChart();
this.drawPieChart();
},
clearCharts() {
[this.$refs.barChart, this.$refs.lineChart, this.$refs.pieChart]
.forEach(ref => ref.innerHTML = '');
},
// 各圖表繪制方法(保持原有實(shí)現(xiàn),開(kāi)頭添加清除邏輯)
// 繪制柱狀圖
drawBarChart() {
const margin = { top: 30, right: 30, bottom: 50, left: 60 };
const width = 800 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select(this.$refs.barChart)
.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})`);
// 創(chuàng)建比例尺
const x = d3.scaleBand()
.domain(this.weatherData.map(d => d.record_date))
.range([0, width])
.padding(0.2);
const y = d3.scaleLinear()
.domain([0, d3.max(this.weatherData, d => d.temperature)])
.range([height, 0]);
// 添加柱狀
svg.selectAll("rect")
.data(this.weatherData)
.join("rect")
.attr("x", d => x(d.record_date))
.attr("y", d => y(d.temperature))
.attr("width", x.bandwidth())
.attr("height", d => height - y(d.temperature))
.attr("fill", "#4CAF50");
// 添加坐標(biāo)軸
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).tickValues(x.domain().filter((d,i) => !(i%5))));
svg.append("g")
.call(d3.axisLeft(y));
// 添加標(biāo)簽
svg.append("text")
.attr("transform", `translate(${width/2}, ${height + 40})`)
.style("text-anchor", "middle")
.text("日期");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("溫度(℃)");
},
// 繪制折線圖
drawLineChart() {
const margin = { top: 30, right: 30, bottom: 50, left: 60 };
const width = 800 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select(this.$refs.lineChart)
.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})`);
// 創(chuàng)建比例尺
const x = d3.scaleBand()
.domain(this.weatherData.map(d => d.record_date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([d3.min(this.weatherData, d => d.temperature) - 2, d3.max(this.weatherData, d => d.temperature) + 2])
.range([height, 0]);
// 創(chuàng)建折線生成器
const line = d3.line()
.x(d => x(d.record_date) + x.bandwidth()/2)
.y(d => y(d.temperature));
// 繪制折線
svg.append("path")
.datum(this.weatherData)
.attr("fill", "none")
.attr("stroke", "#2196F3")
.attr("stroke-width", 2)
.attr("d", line);
// 添加坐標(biāo)軸
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).tickValues(x.domain().filter((d,i) => !(i%5))));
svg.append("g")
.call(d3.axisLeft(y));
},
// 繪制餅圖
drawPieChart() {
const width = 400;
const height = 400;
const radius = Math.min(width, height) / 2;
const svg = d3.select(this.$refs.pieChart)
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width/2},${height/2})`);
// 統(tǒng)計(jì)天氣狀況
const data = Array.from(
d3.rollup(this.weatherData,
v => v.length,
d => d.weather_condition
),
([name, value]) => ({name, value})
);
// 創(chuàng)建顏色比例尺
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(d3.schemeCategory10);
// 餅圖生成器
const pie = d3.pie()
.value(d => d.value);
// 弧形生成器
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.name))
.attr("stroke", "white")
.style("stroke-width", "2px");
// 添加標(biāo)簽
arcs.append("text")
.attr("transform", d => `translate(${arc.centroid(d)})`)
.attr("text-anchor", "middle")
.text(d => d.data.name);
}
}
};
</script>
<style>
.controls {
padding: 1rem;
display: flex;
gap: 1rem;
align-items: center;
}
.controls input {
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
width: 120px;
}
.controls button {
padding: 0.5rem 1rem;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.controls button:hover {
background: #1976D2;
}
.charts-container {
display: flex;
flex-direction: column;
gap: 2rem;
padding: 2rem;
}
.chart-box {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
width: 100%;
}
.chart-box h3 {
margin: 0 0 1rem;
color: #333;
}
.chart {
width: 100%;
height: 400px;
}
</style>
end
//我是分割線
step101: old vue
下面的代碼修改:
1.年份 月份 改為可選,
2.新增兩個(gè)按鈕,查詢和添加
3.查詢和添加,需要做網(wǎng)絡(luò)請(qǐng)求
4.三個(gè)圖表 需要垂直排列
1. 生成天氣數(shù)據(jù)(POST請(qǐng)求):
URL: http://localhost:8000/generate-data/?year=2024&month=3&seed=42
方法: POST
Headers:
Content-Type: application/json
成功響應(yīng)示例:
{
"status": "success",
"message": "成功生成31條天氣數(shù)據(jù)",
"year": 2024,
"month": 3
}
<template>
<div class="charts-container">
<div class="chart-box">
<h3>每日溫度柱狀圖</h3>
<div ref="barChart" class="chart"></div>
</div>
<div class="chart-box">
<h3>溫度趨勢(shì)折線圖</h3>
<div ref="lineChart" class="chart"></div>
</div>
<div class="chart-box">
<h3>天氣狀況分布餅圖</h3>
<div ref="pieChart" class="chart"></div>
</div>
</div>
</template>
<script>
import * as d3 from 'd3';
import axios from 'axios';
export default {
data() {
return {
weatherData: []
};
},
async mounted() {
try {
const response = await axios.get('http://localhost:8000/weather-data/?year=2024&month=4');
this.weatherData = response.data.data;
this.drawBarChart();
this.drawLineChart();
this.drawPieChart();
} catch (error) {
console.error('數(shù)據(jù)獲取失敗:', error);
}
},
methods: {
// 繪制柱狀圖
drawBarChart() {
const margin = { top: 30, right: 30, bottom: 50, left: 60 };
const width = 800 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select(this.$refs.barChart)
.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})`);
// 創(chuàng)建比例尺
const x = d3.scaleBand()
.domain(this.weatherData.map(d => d.record_date))
.range([0, width])
.padding(0.2);
const y = d3.scaleLinear()
.domain([0, d3.max(this.weatherData, d => d.temperature)])
.range([height, 0]);
// 添加柱狀
svg.selectAll("rect")
.data(this.weatherData)
.join("rect")
.attr("x", d => x(d.record_date))
.attr("y", d => y(d.temperature))
.attr("width", x.bandwidth())
.attr("height", d => height - y(d.temperature))
.attr("fill", "#4CAF50");
// 添加坐標(biāo)軸
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).tickValues(x.domain().filter((d,i) => !(i%5))));
svg.append("g")
.call(d3.axisLeft(y));
// 添加標(biāo)簽
svg.append("text")
.attr("transform", `translate(${width/2}, ${height + 40})`)
.style("text-anchor", "middle")
.text("日期");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("溫度(℃)");
},
// 繪制折線圖
drawLineChart() {
const margin = { top: 30, right: 30, bottom: 50, left: 60 };
const width = 800 - margin.left - margin.right;
const height = 400 - margin.top - margin.bottom;
const svg = d3.select(this.$refs.lineChart)
.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})`);
// 創(chuàng)建比例尺
const x = d3.scaleBand()
.domain(this.weatherData.map(d => d.record_date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([d3.min(this.weatherData, d => d.temperature) - 2, d3.max(this.weatherData, d => d.temperature) + 2])
.range([height, 0]);
// 創(chuàng)建折線生成器
const line = d3.line()
.x(d => x(d.record_date) + x.bandwidth()/2)
.y(d => y(d.temperature));
// 繪制折線
svg.append("path")
.datum(this.weatherData)
.attr("fill", "none")
.attr("stroke", "#2196F3")
.attr("stroke-width", 2)
.attr("d", line);
// 添加坐標(biāo)軸
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).tickValues(x.domain().filter((d,i) => !(i%5))));
svg.append("g")
.call(d3.axisLeft(y));
},
// 繪制餅圖
drawPieChart() {
const width = 400;
const height = 400;
const radius = Math.min(width, height) / 2;
const svg = d3.select(this.$refs.pieChart)
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${width/2},${height/2})`);
// 統(tǒng)計(jì)天氣狀況
const data = Array.from(
d3.rollup(this.weatherData,
v => v.length,
d => d.weather_condition
),
([name, value]) => ({name, value})
);
// 創(chuàng)建顏色比例尺
const color = d3.scaleOrdinal()
.domain(data.map(d => d.name))
.range(d3.schemeCategory10);
// 餅圖生成器
const pie = d3.pie()
.value(d => d.value);
// 弧形生成器
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.name))
.attr("stroke", "white")
.style("stroke-width", "2px");
// 添加標(biāo)簽
arcs.append("text")
.attr("transform", d => `translate(${arc.centroid(d)})`)
.attr("text-anchor", "middle")
.text(d => d.data.name);
}
}
};
</script>
<style>
.charts-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 2rem;
padding: 2rem;
}
.chart-box {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.chart-box h3 {
margin: 0 0 1rem;
color: #333;
}
.chart {
width: 100%;
height: 400px;
}
</style>
step102:c++ 模擬數(shù)據(jù)
C:\Users\wangrusheng\source\repos\CMakeProject1\CMakeProject1\CMakeProject1.cpp
#include <iostream>
#include <random>
#include <vector>
#include <string>
#include <iomanip>
#include <cmath>
struct WeatherData {
int day;
double temperature;
int humidity;
double precipitation;
double wind_speed;
std::string condition;
};
// 生成溫度(可指定范圍)
double generate_temperature(std::mt19937& gen, double min_temp = 10.0, double max_temp = 20.0) {
std::uniform_real_distribution<double> dist(min_temp, max_temp);
return std::round(dist(gen) * 10) / 10.0; // 保留1位小數(shù)
}
// 生成濕度(正態(tài)分布)
int generate_humidity(std::mt19937& gen) {
std::normal_distribution<double> dist(60.0, 15.0);
double humidity = dist(gen);
humidity = std::clamp(humidity, 0.0, 100.0);
return static_cast<int>(std::round(humidity));
}
// 生成降水量(20%概率下雨)
double generate_precipitation(std::mt19937& gen) {
std::bernoulli_distribution rain_dist(0.2);
if (rain_dist(gen)) {
std::exponential_distribution<double> amount_dist(1.0 / 5.0); // 平均5mm
double amount = amount_dist(gen);
amount = std::clamp(amount, 0.1, 30.0);
return std::round(amount * 10) / 10.0; // 保留1位小數(shù)
}
return 0.0;
}
// 生成風(fēng)速(伽馬分布)
double generate_wind_speed(std::mt19937& gen) {
std::gamma_distribution<double> dist(2.0, 2.0);
double speed = dist(gen);
speed = std::clamp(speed, 0.0, 20.0);
return std::round(speed * 10) / 10.0; // 保留1位小數(shù)
}
// 生成天氣狀況
std::string get_condition(double temp, double precip, int humidity) {
if (precip > 0) {
return (temp < 3.0) ? "Snow" : "Rain";
}
if (humidity >= 70) return "Cloudy";
if (humidity <= 30) return "Sunny";
// 隨機(jī)選擇部分多云或陰天
static std::vector<std::string> options = { "Partly Cloudy", "Mostly Cloudy" };
std::uniform_int_distribution<int> dist(0, 1);
std::mt19937 temp_gen(std::random_device{}());
return options[dist(temp_gen)];
}
// 生成完整月份數(shù)據(jù)
std::vector<WeatherData> generate_april_data(std::mt19937& gen) {
std::vector<WeatherData> data;
for (int day = 1; day <= 30; ++day) {
WeatherData wd;
wd.day = day;
wd.temperature = generate_temperature(gen);
wd.humidity = generate_humidity(gen);
wd.precipitation = generate_precipitation(gen);
wd.wind_speed = generate_wind_speed(gen);
wd.condition = get_condition(wd.temperature, wd.precipitation, wd.humidity);
data.push_back(wd);
}
return data;
}
int main() {
// 初始化隨機(jī)數(shù)生成器
std::random_device rd;
std::mt19937 gen(rd());
// 生成數(shù)據(jù)
auto weather_data = generate_april_data(gen);
// 輸出CSV格式
std::cout << "Day,Temperature,Humidity,Precipitation,Wind Speed,Condition\n";
for (const auto& wd : weather_data) {
std::cout << wd.day << ","
<< std::fixed << std::setprecision(1) << wd.temperature << "°C,"
<< wd.humidity << "%,"
<< wd.precipitation << "mm,"
<< wd.wind_speed << "m/s,"
<< wd.condition << "\n";
}
return 0;
}到此這篇關(guān)于vue+d3js+fastapi實(shí)現(xiàn)天氣柱狀圖折線圖餅圖的示例的文章就介紹到這了,更多相關(guān)vue+d3js+fastapi柱狀圖折線圖餅圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決echarts圖表y軸數(shù)據(jù)間隔過(guò)大的問(wèn)題
這篇文章主要介紹了解決echarts圖表y軸數(shù)據(jù)間隔過(guò)大的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
使用Webpack提高Vue.js應(yīng)用的方式匯總(四種)
Webpack是開(kāi)發(fā)Vue.js單頁(yè)應(yīng)用程序的重要工具。下面通過(guò)四種方式給大家介紹使用Webpack提高Vue.js應(yīng)用,需要的的朋友參考下吧2017-07-07
vue從一個(gè)頁(yè)面跳轉(zhuǎn)到另一個(gè)頁(yè)面并攜帶參數(shù)的解決方法
這篇文章主要介紹了vue從一個(gè)頁(yè)面跳轉(zhuǎn)到另一個(gè)頁(yè)面并攜帶參數(shù)的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Vue.js實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)數(shù)據(jù)處理
本篇文章主要介紹了Vue.js實(shí)現(xiàn)簡(jiǎn)單動(dòng)態(tài)數(shù)據(jù)處理,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02
Vue項(xiàng)目中Element UI組件未注冊(cè)的問(wèn)題原因及解決方法
在 Vue 項(xiàng)目中使用 Element UI 組件庫(kù)時(shí),開(kāi)發(fā)者可能會(huì)遇到一些常見(jiàn)問(wèn)題,例如組件未正確注冊(cè)導(dǎo)致的警告或錯(cuò)誤,本文將詳細(xì)探討這些問(wèn)題的原因、解決方法,以及如何在需要時(shí)撤銷(xiāo)相關(guān)操作,需要的朋友可以參考下2025-01-01
VUE2 前端實(shí)現(xiàn) 靜態(tài)二級(jí)省市聯(lián)動(dòng)選擇select的示例
下面小編就為大家分享一篇VUE2 前端實(shí)現(xiàn) 靜態(tài)二級(jí)省市聯(lián)動(dòng)選擇select的示例。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
解決element-ui里的下拉多選框 el-select 時(shí),默認(rèn)值不可刪除問(wèn)題
這篇文章主要介紹了解決element-ui里的下拉多選框 el-select 時(shí),默認(rèn)值不可刪除問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
一文詳解Vue3的watch是如何實(shí)現(xiàn)監(jiān)聽(tīng)的
watch這個(gè)API大家都很熟悉,今天這篇文章小編來(lái)帶你搞清楚Vue3的watch是如何實(shí)現(xiàn)對(duì)響應(yīng)式數(shù)據(jù)進(jìn)行監(jiān)聽(tīng)的,希望對(duì)大家有一定的幫助2024-11-11
axios二次封裝的詳細(xì)過(guò)程與跨域問(wèn)題
通常我們的項(xiàng)目會(huì)越做越大,頁(yè)面也會(huì)越來(lái)越多,隨之而來(lái)的是接口數(shù)量的增加,api統(tǒng)一管理,不管接口有多少,所有的接口都可以非常清晰,容易維護(hù),下面這篇文章主要給大家介紹了關(guān)于axios二次封裝的詳細(xì)過(guò)程與跨域問(wèn)題的相關(guān)資料,需要的朋友可以參考下2022-09-09

