ECharts雷达图自定义数据点位置:解决小数值标签重叠的技术创新
技术背景与挑战
问题
- 标签重叠问题:当多个数据点数值较小时,标签会聚集在雷达图中心附近,导致严重重叠
解决方案
- 智能标签定位:自动计算最优标签位置,避免重叠
- 精确坐标转换:实现极坐标系到直角坐标系的精确转换
- 动态布局调整:支持实时重新计算和调整标签位置
- 响应式设计:确保在各种设备上都有良好的显示效果
核心算法实现
1. 极坐标系到直角坐标系转换
雷达图本质上是一个极坐标系统,我们需要将数据点从极坐标转换为屏幕上的直角坐标:
javascript
/**
* 计算数据点在雷达图上的像素坐标
* @param {number} dataIndex - 数据点索引
* @param {number} value - 数据值
* @param {number} maxValue - 最大值
* @returns {Object} 包含x, y坐标和角度的对象
*/
function getDataPointPosition(dataIndex, value, maxValue) {
const { centerX, centerY, radius } = getRadarGeometry();
const angleStep = (2 * Math.PI) / indicators.length; // 角度步长
const angle = (Math.PI / 2) - (dataIndex * angleStep); // 从顶部开始计算角度
const ratio = value / maxValue; // 数值比例
// 极坐标转直角坐标
const x = centerX + (radius * ratio * Math.cos(angle));
const y = centerY - (radius * ratio * Math.sin(angle));
return { x, y, angle };
}
技术要点:
- 使用标准的极坐标转换公式:
x = r*cos(θ)
,y = r*sin(θ)
- 角度计算从顶部(π/2)开始,确保与雷达图视觉一致
- 通过数值比例计算实际半径,实现精确定位
2. 智能标签定位算法
核心创新在于将标签定位在数据点的径向外侧,而非传统的雷达图边缘:
javascript
/**
* 计算数据标签的最优位置
* @returns {Array} 标签位置信息数组
*/
function calculateDataPointLabelPositions() {
const { centerX, centerY, radius } = getRadarGeometry();
const positions = [];
data[0].value.forEach((value, index) => {
const angleStep = (2 * Math.PI) / indicators.length;
const angle = (Math.PI / 2) + (index * angleStep); // 逆时针角度计算
const maxValue = indicators[index].max;
const ratio = value / maxValue;
// 计算数据点实际位置
const dataPointX = centerX + (radius * ratio * Math.cos(angle));
const dataPointY = centerY - (radius * ratio * Math.sin(angle));
// 在数据点外侧放置标签
const labelOffset = 12; // 安全距离
const labelX = dataPointX + (labelOffset * Math.cos(angle));
const labelY = dataPointY - (labelOffset * Math.sin(angle));
positions.push({
x: labelX,
y: labelY,
value: value,
index: index,
angle: angle,
dataPointX: dataPointX,
dataPointY: dataPointY
});
});
return positions;
}
算法优势:
- 径向定位:标签沿着从中心到数据点的径向方向延伸,确保逻辑清晰
- 固定偏移:使用固定的安全距离,避免标签与数据点重叠
- 角度一致性:保持与数据点相同的角度方向,维持视觉连贯性
3. 碰撞检测与避让机制
实现基于欧几里得距离的碰撞检测算法:
javascript
/**
* 碰撞检测函数
* @param {Object} rect1 - 第一个矩形对象
* @param {Object} rect2 - 第二个矩形对象
* @param {number} minDistance - 最小安全距离
* @returns {boolean} 是否发生碰撞
*/
function checkCollision(rect1, rect2, minDistance = 20) {
const dx = Math.abs(rect1.x - rect2.x);
const dy = Math.abs(rect1.y - rect2.y);
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < minDistance;
}
检测机制特点:
- 使用欧几里得距离公式计算精确距离
- 可配置的最小安全距离,适应不同场景需求
- 高效的计算复杂度,支持实时检测
代码实现详解
1. 图表几何参数计算
javascript
function getRadarGeometry() {
const chartDom = document.getElementById('radarChart');
const width = chartDom.offsetWidth;
const height = chartDom.offsetHeight;
const centerX = width * 0.5; // 水平居中
const centerY = height * 0.52; // 略偏下,为标题留空间
const radius = Math.min(width, height) * 0.32; // 自适应半径
return { centerX, centerY, radius, width, height };
}
设计考虑:
- 动态计算容器尺寸,支持响应式设计
- 中心点略向下偏移,为图表标题预留空间
- 半径基于容器最小边长计算,确保图表完整显示
2. 自定义图形元素创建
javascript
function createLabelGraphics() {
if (!showLabels) return [];
const positions = calculateDataPointLabelPositions();
const graphics = [];
positions.forEach((pos, index) => {
graphics.push({
type: 'text',
style: {
text: pos.value.toString(),
x: pos.x,
y: pos.y,
textAlign: 'center',
textVerticalAlign: 'middle',
fontSize: 13,
fontWeight: 'bold',
fill: '#4b6cb7'
},
z: 102 // 确保显示在最上层
});
});
return graphics;
}
实现特色:
- 使用ECharts的graphic组件实现完全自定义的标签
- 支持动态显示/隐藏功能
- 统一的样式配置,确保视觉一致性
- 合理的层级设置,避免被其他元素遮挡
性能优化策略
1. 计算优化
javascript
// 缓存几何参数,避免重复计算
let cachedGeometry = null;
let lastContainerSize = null;
function getRadarGeometry() {
const chartDom = document.getElementById('radarChart');
const currentSize = `${chartDom.offsetWidth}x${chartDom.offsetHeight}`;
if (cachedGeometry && lastContainerSize === currentSize) {
return cachedGeometry;
}
// 重新计算并缓存
cachedGeometry = calculateGeometry();
lastContainerSize = currentSize;
return cachedGeometry;
}
2. 事件处理优化
javascript
// 防抖处理,避免频繁重新计算
let resizeTimer = null;
window.addEventListener('resize', function() {
if (resizeTimer) clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
radarChart.resize();
updateChart();
}, 150);
});
3. 渲染优化
- 按需更新:只在必要时重新计算标签位置
- 批量操作:将多个图形元素合并为单次更新
- 层级管理:合理设置z-index,减少重绘开销
源码(可看效果)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ECharts雷达图小数值优化</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #e4efe9 100%);
color: #333;
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
max-width: 1200px;
width: 100%;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #4b6cb7 0%, #182848 100%);
color: white;
padding: 25px;
text-align: center;
}
.header h1 {
margin: 0;
font-weight: 600;
font-size: 28px;
}
.header p {
margin: 10px 0 0;
opacity: 0.9;
}
.content {
padding: 25px;
display: flex;
flex-direction: column;
gap: 25px;
}
.chart-container {
height: 550px;
width: 100%;
border-radius: 10px;
overflow: hidden;
background: #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
position: relative;
}
.controls {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 15px;
margin-top: 15px;
}
.btn {
padding: 10px 20px;
background: #4b6cb7;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
font-size: 14px;
display: flex;
align-items: center;
gap: 5px;
}
.btn:hover {
background: #395694;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.btn i {
font-size: 16px;
}
.optimization-info {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
border-left: 4px solid #4b6cb7;
}
.optimization-info h2 {
margin-top: 0;
color: #2c3e50;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
}
.optimization-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 15px;
margin-top: 15px;
}
.optimization-item {
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
display: flex;
align-items: flex-start;
gap: 10px;
}
.icon {
background: #4b6cb7;
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.optimization-item h3 {
margin: 0 0 8px;
color: #4b6cb7;
font-size: 16px;
}
.optimization-item p {
margin: 0;
font-size: 14px;
color: #555;
line-height: 1.5;
}
@media (max-width: 768px) {
.optimization-list {
grid-template-columns: 1fr;
}
.controls {
flex-direction: column;
align-items: center;
}
.btn {
width: 80%;
}
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 18px;
color: #4b6cb7;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>ECharts雷达图小数值优化方案</h1>
<p>公司A能力评估 - 解决数值过小导致的标签重叠问题</p>
</div>
<div class="content">
<div class="chart-container" id="radarChart">
<div class="loading">图表加载中...</div>
</div>
<div class="controls">
<button class="btn" id="resetView"><i>↺</i> 重置视图</button>
<button class="btn" id="showValues"><i>📊</i> 显示/隐藏数值</button>
<button class="btn" id="adjustLayout"><i>🔄</i> 调整布局</button>
</div>
<div class="optimization-info">
<h2>优化策略</h2>
<div class="optimization-list">
<div class="optimization-item">
<div class="icon">1</div>
<div>
<h3>外圈标签定位</h3>
<p>将标签精确定位在雷达图最外层圆圈和分割线的交界处,确保清晰可见</p>
</div>
</div>
<div class="optimization-item">
<div class="icon">2</div>
<div>
<h3>交互式控制</h3>
<p>提供多种视图控制选项,包括缩放、重置和标签显示/隐藏功能</p>
</div>
</div>
<div class="optimization-item">
<div class="icon">3</div>
<div>
<h3>视觉优化</h3>
<p>使用清晰的配色方案和半透明填充,提高图表可读性和美观性</p>
</div>
</div>
<div class="optimization-item">
<div class="icon">4</div>
<div>
<h3>响应式设计</h3>
<p>图表自动适应不同屏幕尺寸,确保在各种设备上都有良好的显示效果</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
/**
* ========================================
* ECharts雷达图小数值优化方案
* ========================================
* 功能说明:解决雷达图中小数值导致的标签重叠问题
* 主要特性:智能标签定位、交互式控制、响应式设计
* 作者:开发团队
* 更新时间:2024年
*/
// ========================================
// 1. 图表初始化
// ========================================
// 初始化ECharts雷达图实例
const radarChart = echarts.init(document.getElementById('radarChart'));
// 延迟移除加载提示,提升用户体验
setTimeout(() => {
document.querySelector('.loading').style.display = 'none';
}, 500);
// ========================================
// 2. 数据定义部分
// ========================================
/**
* 雷达图指标配置
* 定义了雷达图的各个维度及其最大值
* name: 指标名称,显示在雷达图的各个顶点
* max: 该指标的最大值,用于计算数据点在雷达图中的相对位置
*/
const indicators = [
{ name: '销售能力', max: 100 }, // 销售团队的业绩表现能力
{ name: '技术实力', max: 100 }, // 技术研发和创新能力
{ name: '客户服务', max: 100 }, // 客户满意度和服务质量
{ name: '研发投入', max: 100 }, // 研发资源投入程度
{ name: '市场占有率', max: 100 }, // 在目标市场的份额占比
{ name: '品牌影响力', max: 100 }, // 品牌知名度和影响力
{ name: '产品质量', max: 100 }, // 产品质量和可靠性
{ name: '用户满意度', max: 100 } // 最终用户的满意程度
];
/**
* 雷达图数据配置
* 包含实际数据值和样式配置
* value: 各指标的实际数值,按indicators顺序排列
* name: 数据系列名称
* itemStyle: 数据点样式配置
* areaStyle: 填充区域样式配置
* lineStyle: 连接线样式配置
*/
const data = [
{
value: [1, 2, 3, 4, 5, 6, 7, 8], // 各指标的实际数值(测试数据)
name: '公司A', // 数据系列名称
itemStyle: {
color: '#4b6cb7' // 数据点颜色
},
areaStyle: {
color: 'rgba(75, 108, 183, 0.2)' // 填充区域颜色(半透明)
},
lineStyle: {
color: '#4b6cb7', // 连接线颜色
width: 2 // 连接线宽度
}
}
];
// ========================================
// 3. 核心算法函数
// ========================================
// 全局变量:用于存储图形元素
let labelGraphics = []; // 存储标签图形元素
let lineGraphics = []; // 存储连接线图形元素
/**
* 计算雷达图的几何参数
* 功能:获取雷达图的中心坐标、半径等关键几何信息
* 返回值:包含centerX, centerY, radius, width, height的对象
* 用途:为后续的坐标计算提供基础参数
*/
function getRadarGeometry() {
const chartDom = document.getElementById('radarChart');
const width = chartDom.offsetWidth; // 图表容器宽度
const height = chartDom.offsetHeight; // 图表容器高度
const centerX = width * 0.5; // 雷达图中心X坐标(水平居中)
const centerY = height * 0.52; // 雷达图中心Y坐标(略偏下,为标题留空间)
const radius = Math.min(width, height) * 0.32; // 雷达图半径(取宽高最小值的32%)
return { centerX, centerY, radius, width, height };
}
/**
* 计算数据点在雷达图上的像素坐标
* 功能:将数据值转换为雷达图上的实际像素坐标
* 参数:
* - dataIndex: 数据点在indicators数组中的索引
* - value: 数据点的实际数值
* - maxValue: 该指标的最大值
* 返回值:包含x, y坐标和角度的对象
*/
function getDataPointPosition(dataIndex, value, maxValue) {
const { centerX, centerY, radius } = getRadarGeometry();
const angleStep = (2 * Math.PI) / indicators.length; // 每个指标之间的角度间隔
const angle = (Math.PI / 2) - (dataIndex * angleStep); // 从顶部开始,顺时针计算角度
const ratio = value / maxValue; // 数值在最大值中的比例
// 使用极坐标转直角坐标的公式计算像素位置
const x = centerX + (radius * ratio * Math.cos(angle));
const y = centerY - (radius * ratio * Math.sin(angle));
return { x, y, angle };
}
/**
* 碰撞检测函数
* 功能:检测两个矩形区域是否发生碰撞
* 参数:
* - rect1, rect2: 两个矩形对象,包含x, y坐标
* - minDistance: 最小安全距离(默认20像素)
* 返回值:布尔值,true表示发生碰撞
* 算法:使用欧几里得距离公式计算两点间距离
*/
function checkCollision(rect1, rect2, minDistance = 20) {
const dx = Math.abs(rect1.x - rect2.x); // X轴距离差
const dy = Math.abs(rect1.y - rect2.y); // Y轴距离差
const distance = Math.sqrt(dx * dx + dy * dy); // 欧几里得距离
return distance < minDistance; // 距离小于最小安全距离则认为碰撞
}
/**
* 计算数据标签在数据点外侧的最优位置
* 功能:为每个数据点计算对应标签的显示位置,确保标签在数据点外侧且不重叠
* 算法流程:
* 1. 遍历所有数据点
* 2. 计算每个数据点的实际像素坐标
* 3. 在数据点的径向外侧放置标签
* 4. 应用固定的安全距离避免重叠
* 返回值:包含所有标签位置信息的数组
*/
function calculateDataPointLabelPositions() {
const { centerX, centerY, radius } = getRadarGeometry();
const positions = []; // 存储所有标签位置的数组
// 遍历第一个数据系列的所有数值
data[0].value.forEach((value, index) => {
const angleStep = (2 * Math.PI) / indicators.length; // 角度步长
const angle = (Math.PI / 2) + (index * angleStep); // 从顶部开始,逆时针计算角度
const maxValue = indicators[index].max; // 当前指标的最大值
const ratio = value / maxValue; // 数值比例
// 步骤1:计算数据点的实际像素位置
// 使用极坐标系转换为直角坐标系
const dataPointX = centerX + (radius * ratio * Math.cos(angle));
const dataPointY = centerY - (radius * ratio * Math.sin(angle));
// 步骤2:在数据点外侧固定距离放置标签
const labelOffset = 12; // 标签与数据点的安全距离(像素)
// 沿着从中心到数据点的径向方向延伸
const labelX = dataPointX + (labelOffset * Math.cos(angle));
const labelY = dataPointY - (labelOffset * Math.sin(angle));
// 步骤3:存储标签位置信息
positions.push({
x: labelX, // 标签X坐标
y: labelY, // 标签Y坐标
value: value, // 标签显示的数值
index: index, // 数据点索引
angle: angle, // 角度信息
dataPointX: dataPointX, // 对应数据点X坐标
dataPointY: dataPointY // 对应数据点Y坐标
});
});
return positions;
}
/**
* 创建数据标签的图形元素
* 功能:使用ECharts的graphic组件创建自定义标签
* 特点:无背景、无引导线,直接显示在数据点外侧
* 返回值:graphic元素数组,用于ECharts配置
* 设计理念:简洁清晰,避免视觉干扰
*/
function createLabelGraphics() {
// 如果标签显示开关关闭,返回空数组
if (!showLabels) return [];
// 获取所有标签的计算位置
const positions = calculateDataPointLabelPositions();
const graphics = []; // 存储图形元素的数组
// 为每个位置创建对应的文字标签
positions.forEach((pos, index) => {
// 创建文字标签元素
graphics.push({
type: 'text', // 图形类型:文字
style: {
text: pos.value.toString(), // 显示的文字内容(数值)
x: pos.x, // 文字X坐标
y: pos.y, // 文字Y坐标
textAlign: 'center', // 水平对齐方式:居中
textVerticalAlign: 'middle', // 垂直对齐方式:居中
fontSize: 13, // 字体大小
fontWeight: 'bold', // 字体粗细:粗体
fill: '#4b6cb7' // 文字颜色:蓝色
},
z: 102 // 层级:确保标签显示在最上层
});
});
return graphics;
}
// ========================================
// 4. 状态管理和配置生成
// ========================================
// 全局状态变量
let showLabels = true; // 标签显示开关
let currentOption; // 当前图表配置选项
/**
* 生成ECharts配置选项
* 功能:创建完整的雷达图配置对象
* 包含:标题、提示框、图例、雷达图配置、数据系列、自定义图形等
* 返回值:ECharts配置对象
* 设计原则:响应式、美观、功能完整
*/
function generateOption() {
return {
// 图表标题配置
title: {
text: '公司A能力评估雷达图(小数值优化)', // 标题文字
left: 'center', // 水平居中
top: 10, // 距离顶部10像素
textStyle: {
color: '#2c3e50', // 标题颜色:深灰色
fontSize: 20, // 字体大小
fontWeight: 'normal' // 字体粗细:正常
}
},
// 提示框配置(鼠标悬停时显示)
tooltip: {
trigger: 'item', // 触发类型:数据项
formatter: function(params) { // 自定义提示框内容格式
return `<b>${params.seriesName}</b><br/>${params.name}: ${params.value}`;
},
backgroundColor: 'rgba(255,255,255,0.9)', // 背景色:半透明白色
borderColor: '#4b6cb7', // 边框颜色:蓝色
textStyle: {
color: '#333' // 文字颜色:深灰色
}
},
// 图例配置
legend: {
data: ['公司A'], // 图例数据
bottom: 10, // 距离底部10像素
itemGap: 25, // 图例项间距
textStyle: {
fontSize: 14 // 图例文字大小
}
},
// 雷达图坐标系配置
radar: {
indicator: indicators, // 雷达图指标配置
radius: '65%', // 雷达图半径(相对于容器)
center: ['50%', '52%'], // 雷达图中心位置
startAngle: 90, // 起始角度(从顶部开始)
splitNumber: 4, // 分割段数
shape: 'circle', // 雷达图形状:圆形
// 坐标轴名称配置
axisName: {
color: '#333', // 文字颜色
backgroundColor: '#f8f9fa', // 背景色:浅灰色
borderRadius: 3, // 圆角半径
padding: [5, 8], // 内边距
fontSize: 12, // 字体大小
formatter: (value) => { // 格式化函数
return `{title|${value}}`;
},
rich: { // 富文本样式
title: {
fontSize: 12,
fontWeight: 'bold',
color: '#2c3e50'
}
}
},
// 分割区域配置
splitArea: {
areaStyle: {
// 交替显示的区域颜色
color: ['rgba(0, 0, 0, 0.02)', 'rgba(0, 0, 0, 0.05)']
}
},
// 坐标轴线配置
axisLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.2)' // 轴线颜色:浅灰色
}
},
// 分割线配置
splitLine: {
lineStyle: {
color: 'rgba(0, 0, 0, 0.2)' // 分割线颜色:浅灰色
}
}
},
// 数据系列配置
series: [{
type: 'radar', // 图表类型:雷达图
data: data, // 数据源
symbolSize: 8, // 数据点大小
lineStyle: {
width: 2 // 连接线宽度
},
areaStyle: {
opacity: 0.3 // 填充区域透明度
},
label: {
show: false // 禁用默认标签(使用自定义标签)
},
emphasis: { // 鼠标悬停时的样式
lineStyle: {
width: 3 // 连接线加粗
},
areaStyle: {
opacity: 0.5 // 填充区域加深
}
}
}],
// 自定义图形元素(用于显示优化后的标签)
graphic: createLabelGraphics() // 调用标签创建函数
};
}
// ========================================
// 5. 图表更新和渲染
// ========================================
/**
* 更新图表函数
* 功能:重新生成配置并更新图表显示
* 用途:响应用户交互、窗口大小变化等事件
* 特点:完全替换配置,确保状态一致性
*/
function updateChart() {
const newOption = generateOption(); // 生成新的配置选项
radarChart.setOption(newOption, true); // 更新图表(true表示不合并,完全替换)
}
// 初始化渲染图表
currentOption = generateOption(); // 生成初始配置
radarChart.setOption(currentOption); // 设置图表配置并渲染
// ========================================
// 6. 交互功能实现
// ========================================
/**
* 重置视图按钮事件处理
* 功能:恢复图表到初始状态
* 操作:显示所有标签,更新按钮文字
*/
document.getElementById('resetView').addEventListener('click', function() {
showLabels = true; // 开启标签显示
updateChart(); // 更新图表
document.getElementById('showValues').innerHTML = '<i>📊</i> 隐藏数值'; // 更新按钮文字
});
/**
* 显示/隐藏数值按钮事件处理
* 功能:切换数据标签的显示状态
* 特点:动态更新按钮文字,提供直观的状态反馈
*/
document.getElementById('showValues').addEventListener('click', function() {
showLabels = !showLabels; // 切换标签显示状态
updateChart(); // 更新图表
// 根据当前状态更新按钮文字
this.innerHTML = showLabels ? '<i>📊</i> 隐藏数值' : '<i>📊</i> 显示数值';
});
/**
* 调整布局按钮事件处理
* 功能:重新计算并应用标签位置
* 用途:手动触发标签位置优化算法
*/
document.getElementById('adjustLayout').addEventListener('click', function() {
updateChart(); // 重新计算并应用标签避让算法
});
// ========================================
// 7. 响应式设计实现
// ========================================
/**
* 窗口大小变化事件处理
* 功能:确保图表在不同屏幕尺寸下正常显示
* 算法:
* 1. 调用ECharts的resize方法调整图表大小
* 2. 延迟100ms后重新计算标签位置
* 3. 避免频繁计算,提升性能
*/
window.addEventListener('resize', function() {
radarChart.resize(); // 调整图表容器大小
// 延迟重新计算标签位置,避免频繁计算影响性能
setTimeout(() => {
updateChart(); // 重新计算标签位置并更新图表
}, 100);
});
/**
* ========================================
* 代码总结
* ========================================
*
* 本雷达图实现的核心特性:
* 1. 智能标签定位:解决小数值导致的标签重叠问题
* 2. 响应式设计:适配不同屏幕尺寸
* 3. 交互式控制:提供多种用户操作选项
* 4. 视觉优化:清晰的配色和布局设计
* 5. 性能优化:合理的事件处理和计算策略
*
* 技术要点:
* - 使用ECharts的graphic组件实现自定义标签
* - 采用极坐标系进行位置计算
* - 实现碰撞检测和避让算法
* - 提供完整的用户交互体验
*/
</script>
</body>
</html>