ECharts雷达图自定义数据点位置

ECharts雷达图自定义数据点位置:解决小数值标签重叠的技术创新

技术背景与挑战

问题

  1. 标签重叠问题:当多个数据点数值较小时,标签会聚集在雷达图中心附近,导致严重重叠

解决方案

  • 智能标签定位:自动计算最优标签位置,避免重叠
  • 精确坐标转换:实现极坐标系到直角坐标系的精确转换
  • 动态布局调整:支持实时重新计算和调整标签位置
  • 响应式设计:确保在各种设备上都有良好的显示效果

核心算法实现

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>
相关推荐
前端日常开发13 分钟前
七夕快到了,看看Trae老师怎么实现浪漫的烟花
trae
前端日常开发31 分钟前
记忆卡牌,锻炼你的瞬间记忆力,让Trae帮你完成这个小游戏的开发
trae
萌萌哒草头将军2 小时前
🚀🚀🚀 告别复制粘贴,这个高效的 Vite 插件让我摸鱼🐟时间更充足了!
前端·vite·trae
Goboy2 小时前
拼图游戏:Trae 轻松实现图片拼接挑战
ai编程·trae
TimelessHaze3 小时前
【performance面试考点】让面试官眼前一亮的performance性能优化
前端·性能优化·trae
bug菌5 小时前
还在为Java API文档熬夜加班?字节Trae让你躺着就能生成专业文档!
aigc·ai编程·trae
bug菌7 小时前
字节Trae替你摆平数据分析,助你释放双手!
aigc·ai编程·trae
豆包MarsCode11 小时前
TRAE MCP 实践:构建全网热点内容创作平台
trae