You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

619 lines
22 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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="/js/echarts5.4.3/echarts.min.js"></script>
<style>
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
padding: 25px;
backdrop-filter: blur(10px);
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 25px;
font-size: 28px;
font-weight: 600;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
}
#windChart {
width: 100%;
height: 650px;
border: 1px solid #e1e8ed;
border-radius: 10px;
background: #fafbfc;
}
.description {
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #3498db;
}
.description p {
margin: 8px 0;
color: #555;
font-size: 14px;
line-height: 1.5;
}
.loading {
text-align: center;
padding: 50px;
font-size: 18px;
color: #666;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 2s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.controls {
margin-bottom: 20px;
text-align: center;
}
.controls button {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
margin: 0 5px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.controls button:hover {
background: #2980b9;
}
.wind-barb-legend {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 20px;
margin: 15px 0;
}
.legend-item {
text-align: center;
font-size: 12px;
}
.legend-barb {
display: inline-block;
margin-bottom: 5px;
}
.color-legend {
display: inline-block;
width: 20px;
height: 15px;
margin-right: 5px;
border: 1px solid #ccc;
border-radius: 2px;
}
</style>
</head>
<body>
<div class="container">
<h1>🌬️ 风矢杆可视化图</h1>
<div class="controls">
<button onclick="refreshData()">🔄 刷新数据</button>
<button onclick="toggleAnimation()">⏯️ 切换动画</button>
</div>
<div id="windChart">
<div class="loading">
<div class="spinner"></div>
<div>图表加载中...</div>
</div>
</div>
<div class="description">
<p><strong>图表说明:</strong></p>
<p><strong>横坐标</strong>时间轴显示24小时的时间序列</p>
<p><strong>纵坐标</strong>高度轴从250米到3850米从下往上递增</p>
<p><strong>风矢杆</strong>:尾端指向风的来向,风羽表示风速</p>
<p><strong>颜色图例</strong>:右侧颜色条表示风速大小</p>
<div class="wind-barb-legend">
<div class="legend-item">
<div class="legend-barb" id="legend-short-feather"></div>
<div>短羽 = 2 m/s</div>
</div>
<div class="legend-item">
<div class="legend-barb" id="legend-long-feather"></div>
<div>长羽 = 4 m/s</div>
</div>
<div class="legend-item">
<div class="legend-barb" id="legend-triangle"></div>
<div>三角旗 = 20 m/s</div>
</div>
<div class="legend-item">
<div class="legend-barb" id="legend-calm"></div>
<div>圆圈 = 静风</div>
</div>
</div>
</div>
</div>
<script>
let chartInstance = null;
let animationEnabled = true;
// 风速颜色映射
const windSpeedColors = [
{ min: 0, max: 2, color: '#98FB98', label: '0-2 m/s' }, // 浅绿色 - 微风
{ min: 2, max: 5, color: '#7EC8E3', label: '2-5 m/s' }, // 浅蓝色 - 轻风
{ min: 5, max: 10, color: '#FFD700', label: '5-10 m/s' }, // 黄色 - 和风
{ min: 10, max: 15, color: '#FFA500', label: '10-15 m/s' }, // 橙色 - 清风
{ min: 15, max: 20, color: '#FF6347', label: '15-20 m/s' }, // 番茄红 - 强风
{ min: 20, max: 25, color: '#DC143C', label: '20-25 m/s' }, // 深红 - 大风
{ min: 25, max: 50, color: '#8B0000', label: '25-50 m/s' } // 暗红 - 狂风
];
// 根据风速获取颜色
function getColorForWindSpeed(speed) {
for (const range of windSpeedColors) {
if (speed >= range.min && speed < range.max) {
return range.color;
}
}
return windSpeedColors[windSpeedColors.length - 1].color; // 默认返回最高风速颜色
}
// 初始化图表
function initChart() {
const chartDom = document.getElementById('windChart');
if (typeof echarts === 'undefined') {
chartDom.innerHTML = `
<div class="loading">
<div style="color: #e74c3c; font-size: 24px;">⚠️</div>
<div>ECharts库加载失败</div>
<div style="font-size: 12px; margin-top: 10px;">请检查网络连接或刷新页面重试</div>
</div>
`;
return;
}
chartInstance = echarts.init(chartDom);
updateChart();
createLegendExamples();
window.addEventListener('resize', function() {
if (chartInstance) {
chartInstance.resize();
}
});
}
// 创建图例示例
function createLegendExamples() {
createWindBarbExample('legend-short-feather', 0, 8, 135); // 2个短羽
createWindBarbExample('legend-long-feather', 0, 16, 135); // 2个长羽
createWindBarbExample('legend-triangle', 0, 40, 135); // 2个三角旗
createWindBarbExample('legend-calm', 0, 0, 0); // 静风
}
// 创建风矢杆示例
function createWindBarbExample(elementId, direction, speed, exampleDirection) {
const canvas = document.createElement('canvas');
canvas.width = 60;
canvas.height = 60;
canvas.style.width = '60px';
canvas.style.height = '60px';
const ctx = canvas.getContext('2d');
ctx.translate(30, 30);
if (speed === 0) {
// 静风 - 画圆圈
ctx.beginPath();
ctx.arc(0, 0, 8, 0, 2 * Math.PI);
ctx.strokeStyle = '#000';
ctx.lineWidth = 1.5;
ctx.stroke();
} else {
// 风矢杆
drawWindBarb(ctx, exampleDirection, speed, 20, true);
}
document.getElementById(elementId).appendChild(canvas);
}
// 绘制风矢杆
function drawWindBarb(ctx, direction, speed, barbLength, isExample = false) {
const radian = (direction - 180) * Math.PI / 180; // 指向风的来向
// 根据风速获取颜色
const color = getColorForWindSpeed(speed);
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 1.5;
ctx.lineCap = 'round';
if (speed === 0) {
// 静风 - 画圆圈
ctx.beginPath();
ctx.arc(0, 0, 4, 0, 2 * Math.PI);
ctx.stroke();
return;
}
// 绘制主杆
ctx.beginPath();
ctx.moveTo(0, 0);
const endX = Math.cos(radian) * barbLength;
const endY = Math.sin(radian) * barbLength;
ctx.lineTo(endX, endY);
ctx.stroke();
// 绘制风羽
let remainingSpeed = speed;
let position = 0;
const spacing = isExample ? 6 : 4;
// 绘制三角旗 (20 m/s)
const triangles = Math.floor(remainingSpeed / 20);
remainingSpeed %= 20;
for (let i = 0; i < triangles; i++) {
const posX = Math.cos(radian) * (position * spacing);
const posY = Math.sin(radian) * (position * spacing);
const perpendicular = radian + Math.PI / 2;
const triangleSize = isExample ? 8 : 6;
ctx.beginPath();
ctx.moveTo(posX, posY);
ctx.lineTo(
posX + Math.cos(perpendicular) * triangleSize,
posY + Math.sin(perpendicular) * triangleSize
);
ctx.lineTo(
posX + Math.cos(radian) * triangleSize + Math.cos(perpendicular) * triangleSize,
posY + Math.sin(radian) * triangleSize + Math.sin(perpendicular) * triangleSize
);
ctx.closePath();
ctx.fill();
position += 2;
}
// 绘制长羽 (4 m/s)
const longFeathers = Math.floor(remainingSpeed / 4);
remainingSpeed %= 4;
for (let i = 0; i < longFeathers; i++) {
const posX = Math.cos(radian) * (position * spacing);
const posY = Math.sin(radian) * (position * spacing);
const perpendicular = radian + Math.PI / 2;
const featherLength = isExample ? 10 : 8;
ctx.beginPath();
ctx.moveTo(posX, posY);
ctx.lineTo(
posX + Math.cos(perpendicular) * featherLength,
posY + Math.sin(perpendicular) * featherLength
);
ctx.stroke();
position += 1;
}
// 绘制短羽 (2 m/s)
const shortFeathers = Math.floor(remainingSpeed / 2);
for (let i = 0; i < shortFeathers; i++) {
const posX = Math.cos(radian) * (position * spacing);
const posY = Math.sin(radian) * (position * spacing);
const perpendicular = radian + Math.PI / 2;
const featherLength = isExample ? 6 : 5;
ctx.beginPath();
ctx.moveTo(posX, posY);
ctx.lineTo(
posX + Math.cos(perpendicular) * featherLength,
posY + Math.sin(perpendicular) * featherLength
);
ctx.stroke();
position += 1;
}
}
// 生成精确数据
function generateData() {
const times = [];
const heights = [];
const data = [];
// 生成时间序列12个时间点
const now = new Date();
for (let i = 11; i >= 0; i--) {
const time = new Date(now.getTime() - i * 2 * 60 * 60 * 1000);
times.push(time.toLocaleTimeString('zh-CN', {hour: '2-digit', minute: '2-digit'}));
}
// 生成高度序列从250m到3850m每240m一个点
for (let h = 250; h <= 3850; h += 240) {
heights.push(h);
}
// 根据您提供的数据生成精确的风向风速
for (let i = 0; i < times.length; i++) {
for (let j = 0; j < heights.length; j++) {
const height = heights[j];
let direction, speed;
// 根据高度设置风向风速
if (height === 250) {
// 250m: 东南风135°, 14m/s (3个长羽1个短羽)
direction = 135;
speed = 14;
} else if (height === 490) {
// 490m: 西北风315°, 16m/s (1个长羽)
direction = 315;
speed = 16;
} else if (height >= 730 && height <= 2170) {
// 730m至2170m: 东北风45°, 8m/s (1个长羽1个短羽)
direction = 45;
speed = 8;
} else if (height >= 2410 && height <= 3850) {
// 2410m至3850m: 东风90°, 45m/s (1个三角旗1个长羽1个短羽)
direction = 90;
speed = 45;
} else {
// 其他高度使用随机数据
const directions = [0, 45, 90, 135, 180, 225, 270, 315];
direction = directions[Math.floor(Math.random() * directions.length)];
speed = Math.floor(Math.random() * 20) + 5;
}
data.push([i, j, direction, speed]);
}
}
return { times, heights, data };
}
// 渲染风矢杆项目
function renderWindBarb(params, api) {
const xIndex = api.value(0);
const yIndex = api.value(1);
const direction = api.value(2);
const speed = api.value(3);
const point = api.coord([xIndex, yIndex]);
const barbLength = 25; // 风矢杆长度
const group = {
type: 'group',
children: [],
silent: false
};
// 创建canvas绘制风矢杆
const canvas = document.createElement('canvas');
canvas.width = 60;
canvas.height = 60;
const ctx = canvas.getContext('2d');
ctx.translate(30, 30);
drawWindBarb(ctx, direction, speed, barbLength);
group.children.push({
type: 'image',
style: {
image: canvas,
x: point[0] - 30,
y: point[1] - 30,
width: 60,
height: 60
}
});
return group;
}
// 更新图表
function updateChart() {
if (!chartInstance) return;
const { times, heights, data } = generateData();
const option = {
title: {
text: '🌬️ 风矢杆分布图 - 精确数据',
left: 'center',
textStyle: {
fontSize: 18,
fontWeight: 'bold',
color: '#2c3e50'
}
},
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(255,255,255,0.95)',
borderColor: '#ccc',
borderWidth: 1,
textStyle: {
color: '#333'
},
formatter: function(params) {
const time = times[params.value[0]];
const height = heights[params.value[1]];
const direction = params.value[2];
const speed = params.value[3];
const directionText = getDirectionText(direction);
const barbDescription = getWindBarbDescription(speed);
return `
<div style="font-weight: bold; margin-bottom: 5px;">${time}</div>
<div>高度: <b>${height}m</b></div>
<div>风向: <b>${direction}° ${directionText}</b></div>
<div>风速: <b>${speed} m/s</b></div>
<div style="margin-top: 5px; font-size: 12px; color: #666;">
${barbDescription}
</div>
`;
}
},
grid: {
left: '70px',
right: '100px',
bottom: '70px',
top: '70px',
containLabel: false
},
xAxis: {
type: 'category',
data: times,
name: '时间',
nameLocation: 'middle',
nameGap: 35,
axisLabel: {
interval: 1,
rotate: 45,
fontSize: 10
},
axisLine: {
lineStyle: {
color: '#7f8c8d'
}
}
},
yAxis: {
type: 'category',
data: heights.map(h => h + 'm'),
name: '高度 (m)',
nameLocation: 'middle',
nameGap: 50,
axisLine: {
lineStyle: {
color: '#7f8c8d'
}
},
inverse: false // 从下往上递增
},
visualMap: {
type: 'piecewise',
min: 0,
max: 50,
orient: 'vertical',
right: 10,
top: 'center',
textStyle: {
color: '#2c3e50',
fontSize: 11
},
pieces: windSpeedColors,
name: '风速\n(m/s)',
nameTextStyle: {
fontSize: 12,
fontWeight: 'bold'
},
formatter: function(value) {
// 自定义格式化显示
const range = windSpeedColors.find(range =>
value.min === range.min && value.max === range.max
);
return range ? range.label : '';
}
},
series: [{
name: '风矢杆',
type: 'custom',
renderItem: renderWindBarb,
data: data,
encode: {
x: [0],
y: [1]
},
animation: animationEnabled,
animationDuration: 1000,
animationEasing: 'cubicOut'
}]
};
chartInstance.setOption(option, true);
}
// 获取风向文本描述
function getDirectionText(direction) {
const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
const index = Math.round(direction / 45) % 8;
return directions[index];
}
// 获取风羽描述
function getWindBarbDescription(speed) {
if (speed === 0) return '静风';
let description = '风羽: ';
let remaining = speed;
const triangles = Math.floor(remaining / 20);
remaining %= 20;
if (triangles > 0) description += `${triangles}个三角旗 `;
const longFeathers = Math.floor(remaining / 4);
remaining %= 4;
if (longFeathers > 0) description += `${longFeathers}个长羽 `;
const shortFeathers = Math.floor(remaining / 2);
if (shortFeathers > 0) description += `${shortFeathers}个短羽`;
return description.trim();
}
// 刷新数据
function refreshData() {
if (chartInstance) {
updateChart();
}
}
// 切换动画
function toggleAnimation() {
animationEnabled = !animationEnabled;
if (chartInstance) {
updateChart();
}
}
// 页面加载完成后初始化图表
document.addEventListener('DOMContentLoaded', function() {
setTimeout(initChart, 100);
});
</script>
</body>
</html>