ECharts 世界地图实现完整指南

文章目录

    • 前言
    • 技术栈
    • 一、项目结构与数据模型
      • [1.1 核心数据结构](#1.1 核心数据结构)
      • [1.2 Mock 数据示例](#1.2 Mock 数据示例)
    • 二、地图初始化
      • [2.1 注册地图数据](#2.1 注册地图数据)
      • [2.2 创建图表实例](#2.2 创建图表实例)
    • 三、国家区域渲染
      • [3.1 数据处理逻辑](#3.1 数据处理逻辑)
      • [3.2 生成地图区域数据](#3.2 生成地图区域数据)
      • [3.3 地图配置项](#3.3 地图配置项)
    • 四、散点特效实现
      • [4.1 国家坐标映射表](#4.1 国家坐标映射表)
      • [4.2 生成散点数据](#4.2 生成散点数据)
      • [4.3 散点图系列配置](#4.3 散点图系列配置)
      • [4.4 散点与地图的层级关系](#4.4 散点与地图的层级关系)
    • 五、交互功能实现
      • [5.1 地图点击事件](#5.1 地图点击事件)
      • [5.2 地图聚焦功能](#5.2 地图聚焦功能)
      • [5.3 鼠标悬停效果](#5.3 鼠标悬停效果)
      • [5.4 动态更新地图数据](#5.4 动态更新地图数据)
    • 六、响应式处理
      • [6.1 窗口大小变化](#6.1 窗口大小变化)
      • [6.2 数据加载时机控制](#6.2 数据加载时机控制)
    • 七、样式与布局
      • [7.1 容器样式](#7.1 容器样式)
    • 八、性能优化建议
      • [8.1 地图数据优化](#8.1 地图数据优化)
      • [8.2 事件节流](#8.2 事件节流)
      • [8.3 按需加载](#8.3 按需加载)
    • 九、常见问题与解决方案
      • [9.1 国家名称不匹配](#9.1 国家名称不匹配)
      • [9.2 散点位置不准确](#9.2 散点位置不准确)
      • [9.3 地图层级混乱](#9.3 地图层级混乱)
      • [9.4 地图不响应窗口变化](#9.4 地图不响应窗口变化)
    • 十、完整示例代码
    • 十一、总结

前言

在现代 Web 应用中,地图可视化是一个常见且重要的需求。本文将详细介绍如何使用 ECharts 实现一个功能完善的世界地图,包括国家

区域渲染、散点标记、交互效果等核心功能。

本文基于真实项目经验,聚焦于以下核心内容:

  • 🗺️ 世界地图的初始化与配置
  • 🎨 国家区域的差异化渲染
  • ✨ 散点特效的实现
  • 🖱️ 地图交互与事件处理
  • 📊 数据格式与状态管理

技术栈

  • Vue 3 - 前端框架
  • ECharts 5.x - 图表库
  • TypeScript - 类型支持
  • @amcharts/amcharts5-geodata - 地图数据源

一、项目结构与数据模型

1.1 核心数据结构

typescript 复制代码
// 国家信息接口
interface CountryInfo {
    code: string; // 国家代码(如 "US", "CN")
    name: string; // 中文名称
    nameEn: string; // 英文名称
    available: boolean; // 是否可选
    coordinates?: [number, number]; // 地理坐标
}

// 活动信息接口(Mock 数据)
interface ActivityInfo {
    rowId: string;
    activityName: string;
    country: string;
    lastUpdateDate: string;
}

1.2 Mock 数据示例

typescript 复制代码
// Mock 国家选项数据
const mockCountryOptions = [
    {
        value: 'US',
        territoryCode: 'US',
        territoryShortName: '美国',
        territoryShortNameEn: 'United States',
    },
    {
        value: 'CN',
        territoryCode: 'CN',
        territoryShortName: '中国',
        territoryShortNameEn: 'China',
    },
    {
        value: 'GB',
        territoryCode: 'GB',
        territoryShortName: '英国',
        territoryShortNameEn: 'United Kingdom',
    },
    // ... 更多国家
];

// Mock 活动数据
const mockActivityList = [
    {
        rowId: 'activity-001',
        activityName: 'Summer Campaign 2024',
        country: 'US',
        lastUpdateDate: '2024-01-15',
    },
    {
        rowId: 'activity-002',
        activityName: 'Spring Festival Event',
        country: 'CN',
        lastUpdateDate: '2024-01-20',
    },
    // ... 更多活动
];

二、地图初始化

2.1 注册地图数据

ECharts 需要先注册地图数据才能使用。我们使用 @amcharts/amcharts5-geodata 提供的世界地图数据:

typescript 复制代码
import * as echarts from 'echarts';
import worldLow from '@amcharts/amcharts5-geodata/worldLow';

// 注册世界地图
echarts.registerMap('world', worldLow, {
    nameProperty: 'id', // 使用 id 作为国家标识符
});

关键点说明:

  • nameProperty: 'id' 指定使用 GeoJSON 中的 id 字段作为国家标识
  • worldLow 是低精度地图数据,文件体积小,适合 Web 应用
  • 国家 ID 使用 ISO 3166-1 alpha-2 标准(如 US、CN、GB)

2.2 创建图表实例

typescript 复制代码
const chartRef = ref<HTMLElement>();
let chartInstance: echarts.ECharts | null = null;

const initChart = async () => {
    if (!chartRef.value) return;

    try {
        loading.value = true;

        // 注册地图
        echarts.registerMap('world', worldLow, {
            nameProperty: 'id',
        });

        // 创建实例
        chartInstance = echarts.init(chartRef.value);

        // 设置配置项
        const option = {
            // ... 配置内容
        };

        chartInstance.setOption(option);

        // 绑定事件
        chartInstance.on('click', handleMapClick);
        chartInstance.on('mouseover', handleMouseOver);
        chartInstance.on('mouseout', handleMouseOut);

        // 监听窗口大小变化
        window.addEventListener('resize', handleResize);
    } catch (error) {
        console.error('地图初始化失败:', error);
    } finally {
        loading.value = false;
    }
};

三、国家区域渲染

3.1 数据处理逻辑

根据业务需求,我们需要将国家分为三种状态:

  1. 已选择 - 用户已选择的国家
  2. 可选择 - 有活动的国家
  3. 不可选 - 没有活动的国家
typescript 复制代码
// 计算可用国家映射表
const availableCountries = computed(() => {
    const map: Record<string, CountryInfo> = {};

    // 获取所有有活动的国家编码
    const availableCodes = new Set(mockActivityList.map((item) => item.country));

    mockCountryOptions.forEach((item) => {
        const key = item.territoryCode;

        if (key) {
            map[key] = {
                code: item.value,
                name: item.territoryShortName,
                nameEn: item.territoryShortNameEn,
                available: availableCodes.has(item.value),
            };
        }
    });

    return map;
});

3.2 生成地图区域数据

typescript 复制代码
const generateMapData = () => {
    const mapData: Array<{
        name: string;
        value: number;
        selected: boolean;
        itemStyle: {
            areaColor: string;
            borderColor: string;
            borderWidth: number;
        };
    }> = [];

    Object.entries(availableCountries.value).forEach(([territoryCode, countryInfo]) => {
        const isSelected = selectedCountry.value?.code === countryInfo.code;
        let value: number;
        let areaColor: string;

        // 根据状态设置颜色
        if (isSelected) {
            value = 2;
            areaColor = '#72B1FF'; // 已选择:深蓝色
        } else if (countryInfo.available) {
            value = 1;
            areaColor = '#BCDAFF'; // 可选择:浅蓝色
        } else {
            value = 0;
            areaColor = '#D5DFED'; // 不可选:灰色
        }

        mapData.push({
            name: territoryCode, // 使用 territoryCode(如 "US")
            value,
            selected: isSelected,
            itemStyle: {
                areaColor,
                borderColor: '#fff',
                borderWidth: 0.5,
            },
        });
    });

    return mapData;
};

3.3 地图配置项

typescript 复制代码
const option = {
    tooltip: {
        trigger: 'item',
        formatter: (params) => {
            // 散点图不显示 tooltip
            if (params.seriesType === 'effectScatter') {
                return null;
            }

            // 获取活动名称
            const activity = mockActivityList.find((item) => item.country === params.name);

            if (activity) {
                return `<div style="
          padding: 8px 12px;
          background: #fff;
          border: 2px solid #1890ff;
          border-radius: 6px;
          box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15);
          font-size: 14px;
          color: #333;
          font-weight: 500;
        ">活动名称: ${activity.activityName}</div>`;
            }
            return null;
        },
        backgroundColor: 'transparent',
        borderWidth: 0,
        padding: 0,
    },
    geo: {
        map: 'world',
        roam: true, // 允许缩放和平移
        scaleLimit: {
            min: 0.8,
            max: 50,
        },
        nameProperty: 'id',
        zoom: 1.2,
        center: [0, 20], // 地图中心点
        zlevel: 0,
        itemStyle: {
            borderColor: '#fff',
            borderWidth: 0.5,
        },
        label: {
            show: true,
            color: '#333',
            fontSize: 10,
            offset: [0, -15], // 向上偏移,避免被散点遮挡
            formatter: (params) => {
                const countryInfo = availableCountries.value[params.name];
                if (countryInfo && countryInfo.available) {
                    // 根据语言显示国家名称
                    return countryInfo.name; // 或 countryInfo.nameEn
                }
                return '';
            },
        },
        emphasis: {
            itemStyle: {
                areaColor: '#72B1FF',
                borderColor: '#ffffff',
                borderWidth: 2,
                shadowBlur: 10,
                shadowColor: 'rgba(0, 0, 0, 0.12)',
            },
            label: {
                show: true,
                color: '#000000',
                fontWeight: 'bold',
                fontSize: 12,
            },
        },
        select: {
            itemStyle: {
                areaColor: '#1890ff',
                borderColor: '#096dd9',
                borderWidth: 2,
            },
            label: {
                show: true,
                color: '#fff',
                fontWeight: 'bold',
                fontSize: 12,
            },
        },
        regions: generateMapData(), // 设置区域数据
    },
    series: [
        // 散点图系列(下一节详细介绍)
    ],
};

配置项关键说明:

配置项 说明
roam: true 允许用户缩放和拖拽地图
scaleLimit 限制缩放范围,防止过度缩放
nameProperty: 'id' 指定使用 GeoJSON 的 id 字段匹配国家
regions 设置各区域的样式和数据
emphasis 鼠标悬停时的样式
select 选中时的样式

四、散点特效实现

4.1 国家坐标映射表

为了在地图上精确标记国家位置,我们需要维护一个国家中心点坐标映射表:

typescript 复制代码
// 国家地理中心点坐标映射(使用 territoryCode 作为 key)
const countryCoordinates: Record<string, [number, number]> = {
    // 亚洲
    AF: [67.7099, 33.9391], // Afghanistan
    CN: [104.1954, 35.8617], // China
    IN: [78.9629, 20.5937], // India
    JP: [138.2529, 36.2048], // Japan
    KR: [127.7669, 35.9078], // South Korea

    // 欧洲
    GB: [-3.436, 55.3781], // United Kingdom
    FR: [2.2137, 46.2276], // France
    DE: [10.4515, 51.1657], // Germany
    IT: [12.5674, 41.8719], // Italy
    ES: [-3.7492, 40.4637], // Spain

    // 北美洲
    US: [-95.7129, 37.0902], // United States
    CA: [-106.3468, 56.1304], // Canada
    MX: [-102.5528, 23.6345], // Mexico

    // 南美洲
    BR: [-51.9253, -14.235], // Brazil
    AR: [-63.6167, -38.4161], // Argentina

    // 大洋洲
    AU: [133.7751, -25.2744], // Australia
    NZ: [174.886, -40.9006], // New Zealand

    // ... 更多国家坐标
};

坐标格式说明:

  • 格式:[经度, 纬度]
  • 经度范围:-180 到 180(西经为负,东经为正)
  • 纬度范围:-90 到 90(南纬为负,北纬为正)

4.2 生成散点数据

typescript 复制代码
const generateScatterData = () => {
    const scatterData: Array<{
        name: string;
        value: [number, number];
    }> = [];

    // 遍历所有可用国家
    Object.entries(availableCountries.value).forEach(([territoryCode, countryInfo]) => {
        // 只为可选且未被选中的国家添加标识
        if (countryInfo.available && selectedCountry.value?.code !== countryInfo.code) {
            const coordinates = countryCoordinates[territoryCode];
            if (coordinates) {
                scatterData.push({
                    name: territoryCode,
                    value: coordinates, // [经度, 纬度]
                });
            }
        }
    });

    return scatterData;
};

数据格式示例:

javascript 复制代码
[
    { name: 'US', value: [-95.7129, 37.0902] },
    { name: 'CN', value: [104.1954, 35.8617] },
    { name: 'GB', value: [-3.436, 55.3781] },
    // ...
];

4.3 散点图系列配置

typescript 复制代码
series: [
    {
        name: '可选国家标识',
        type: 'effectScatter', // 涟漪特效散点图
        coordinateSystem: 'geo', // 使用地理坐标系
        rippleEffect: {
            brushType: 'stroke', // 涟漪效果类型
            scale: 3, // 涟漪动画的缩放比例
            period: 4, // 动画周期(秒)
        },
        symbol: 'circle', // 散点形状
        symbolSize: 12, // 散点大小
        itemStyle: {
            color: '#1890ff', // 散点颜色
            shadowBlur: 10,
            shadowColor: 'rgba(24, 144, 255, 0.5)',
        },
        data: generateScatterData(),
        zlevel: 0,
        z: 1, // 层级(确保在地图上方)
        label: {
            show: false, // 不显示标签
        },
    },
];

效果说明:

  • effectScatter 类型会产生涟漪动画效果
  • rippleEffect 控制涟漪的样式和速度
  • zlevelz 控制图层顺序,确保散点在地图上方

4.4 散点与地图的层级关系

复制代码
┌─────────────────────────────────┐
│  散点图层 (z: 1, zlevel: 0)     │  ← 最上层,显示涟漪特效
├─────────────────────────────────┤
│  地图区域 (zlevel: 0)           │  ← 中间层,显示国家区域
├─────────────────────────────────┤
│  背景层                          │  ← 底层
└─────────────────────────────────┘

五、交互功能实现

5.1 地图点击事件

typescript 复制代码
const handleMapClick = (params: { name: string }) => {
    // params.name 是 territoryCode(如 "US")
    const countryInfo = availableCountries.value[params.name];

    if (!countryInfo || !countryInfo.available) {
        // 不可选的国家,忽略点击
        return;
    }

    // 检查是否已选择
    const isSelected = selectedCountry.value?.code === countryInfo.code;

    if (isSelected) {
        // 已选择,取消选择
        handleClearSelection();
    } else {
        // 未选择,聚焦到该国家
        focusOnCountry(params.name);
        currentCountry.value = countryInfo;
        // 触发业务逻辑(如显示弹窗)
    }
};

5.2 地图聚焦功能

typescript 复制代码
const focusOnCountry = (territoryCode: string) => {
    if (!chartInstance) return;

    const coordinates = countryCoordinates[territoryCode];
    if (coordinates) {
        // 设置地图中心点和缩放级别
        chartInstance.setOption({
            geo: {
                center: coordinates,
                zoom: 3, // 放大到合适的级别
            },
        });
    }
};

// 重置地图视图
const resetMapView = () => {
    if (chartInstance) {
        chartInstance.setOption({
            geo: {
                center: [0, 20],
                zoom: 1.2,
            },
        });
    }
};

5.3 鼠标悬停效果

typescript 复制代码
// 鼠标悬停事件
chartInstance.on('mouseover', (params: { name: string }) => {
    const countryInfo = availableCountries.value[params.name];
    if (countryInfo && countryInfo.available) {
        // 可选国家,改变鼠标样式
        if (chartRef.value?.style) {
            chartRef.value.style.cursor = 'pointer';
        }
    } else if (chartRef.value?.style) {
        chartRef.value.style.cursor = 'default';
    }
});

// 鼠标移出事件
chartInstance.on('mouseout', () => {
    if (chartRef.value?.style) {
        chartRef.value.style.cursor = 'default';
    }
});

5.4 动态更新地图数据

typescript 复制代码
const updateMapData = () => {
    nextTick(() => {
        if (chartInstance) {
            const newMapData = generateMapData();
            const newScatterData = generateScatterData();

            chartInstance.setOption({
                geo: {
                    regions: newMapData,
                },
                series: [
                    {
                        data: newScatterData,
                    },
                ],
            });
        }
    });
};

六、响应式处理

6.1 窗口大小变化

typescript 复制代码
const handleResize = () => {
    if (chartInstance) {
        chartInstance.resize();
    }
};

// 在初始化时绑定
window.addEventListener('resize', handleResize);

// 在组件卸载时解绑
onUnmounted(() => {
    if (chartInstance) {
        chartInstance.dispose();
        chartInstance = null;
    }
    window.removeEventListener('resize', handleResize);
});

6.2 数据加载时机控制

typescript 复制代码
const isChartInitialized = ref(false);

// 监听数据源,确保都准备好后再初始化
watch(
    [activityList, countryOptions, availableCountries],
    ([activities, countries, available]) => {
        const hasActivityList = activities && activities.length > 0;
        const hasCountryOptions = countries && countries.length > 0;
        const hasAvailableCountries = available && Object.keys(available).length > 0;

        if (hasActivityList && hasCountryOptions && hasAvailableCountries && !isChartInitialized.value) {
            isChartInitialized.value = true;
            initChart();
        }
    },
    { immediate: true, deep: true }
);

七、样式与布局

7.1 容器样式

vue 复制代码
<template>
    <div class="country-selection-container">
        <div class="map-container">
            <div
                ref="chartRef"
                class="echarts-container"
            ></div>

            <!-- 加载状态 -->
            <div
                v-if="loading"
                class="loading-overlay"
            >
                <p>正在加载地图数据...</p>
            </div>
        </div>
    </div>
</template>

<style scoped lang="scss">
    .country-selection-container {
        position: relative;
        display: flex;
        flex-direction: column;
        height: calc(100vh - 124px);
    }

    .map-container {
        position: relative;
        flex: 1;
        overflow: hidden;
        background: #edf2f9;
        border-radius: 8px;
    }

    .echarts-container {
        width: 100%;
        height: 100%;
        min-height: 500px;
    }

    .loading-overlay {
        position: absolute;
        inset: 0;
        z-index: 10;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        background: rgba(255, 255, 255, 0.9);
    }

    /* 响应式设计 */
    @media (max-width: 768px) {
        .country-selection-container {
            padding: 10px;
        }

        .echarts-container {
            min-height: 400px;
        }
    }
</style>

八、性能优化建议

8.1 地图数据优化

typescript 复制代码
// 使用低精度地图数据
import worldLow from '@amcharts/amcharts5-geodata/worldLow';

// 而不是高精度数据
// import world from '@amcharts/amcharts5-geodata/world';

8.2 事件节流

typescript 复制代码
import { debounce } from 'lodash-es';

const handleResize = debounce(() => {
    if (chartInstance) {
        chartInstance.resize();
    }
}, 200);

8.3 按需加载

typescript 复制代码
// 动态导入地图数据
const initChart = async () => {
    const worldLow = await import('@amcharts/amcharts5-geodata/worldLow');
    echarts.registerMap('world', worldLow.default, {
        nameProperty: 'id',
    });
    // ...
};

九、常见问题与解决方案

9.1 国家名称不匹配

问题: GeoJSON 中的国家 ID 与业务数据中的国家代码不一致。

解决方案:

typescript 复制代码
// 使用 nameProperty 指定匹配字段
echarts.registerMap('world', worldLow, {
    nameProperty: 'id', // 使用 id 字段
});

// 确保数据中的 name 字段与 GeoJSON 的 id 一致
mapData.push({
    name: territoryCode, // 使用 ISO 3166-1 alpha-2 代码
    value: 1,
    // ...
});

9.2 散点位置不准确

问题: 散点没有显示在国家中心位置。

解决方案:

typescript 复制代码
// 确保坐标格式正确:[经度, 纬度]
const coordinates = [104.1954, 35.8617]; // 正确
// 而不是 [纬度, 经度]

// 确保使用正确的坐标系
series: [
    {
        type: 'effectScatter',
        coordinateSystem: 'geo', // 必须指定
        data: scatterData,
    },
];

9.3 地图层级混乱

问题: 散点被地图区域遮挡。

解决方案:

typescript 复制代码
geo: {
  zlevel: 0,
  // ...
},
series: [{
  type: 'effectScatter',
  zlevel: 0,
  z: 1, // 确保 z 值大于地图
  // ...
}]

9.4 地图不响应窗口变化

问题: 窗口大小改变时地图不自适应。

解决方案:

typescript 复制代码
// 监听窗口变化
window.addEventListener('resize', () => {
    chartInstance?.resize();
});

// 组件卸载时清理
onUnmounted(() => {
    window.removeEventListener('resize', handleResize);
});

十、完整示例代码

vue 复制代码
<template>
    <div class="map-wrapper">
        <div
            ref="chartRef"
            class="chart-container"
        ></div>
    </div>
</template>

<script setup lang="ts">
    import { ref, computed, onMounted, onUnmounted } from 'vue';
    import * as echarts from 'echarts';
    import worldLow from '@amcharts/amcharts5-geodata/worldLow';

    // Mock 数据
    const mockCountryOptions = [
        { value: 'US', territoryCode: 'US', territoryShortName: '美国', territoryShortNameEn: 'United States' },
        { value: 'CN', territoryCode: 'CN', territoryShortName: '中国', territoryShortNameEn: 'China' },
        { value: 'GB', territoryCode: 'GB', territoryShortName: '英国', territoryShortNameEn: 'United Kingdom' },
    ];

    const mockActivityList = [
        { country: 'US', activityName: 'Summer Campaign' },
        { country: 'CN', activityName: 'Spring Festival' },
    ];

    // 响应式数据
    const chartRef = ref<HTMLElement>();
    let chartInstance: echarts.ECharts | null = null;

    // 可用国家映射
    const availableCountries = computed(() => {
        const map: Record<string, any> = {};
        const availableCodes = new Set(mockActivityList.map((item) => item.country));

        mockCountryOptions.forEach((item) => {
            map[item.territoryCode] = {
                code: item.value,
                name: item.territoryShortName,
                nameEn: item.territoryShortNameEn,
                available: availableCodes.has(item.value),
            };
        });

        return map;
    });

    // 国家坐标
    const countryCoordinates: Record<string, [number, number]> = {
        US: [-95.7129, 37.0902],
        CN: [104.1954, 35.8617],
        GB: [-3.436, 55.3781],
    };

    // 生成地图数据
    const generateMapData = () => {
        return Object.entries(availableCountries.value).map(([code, info]) => ({
            name: code,
            value: info.available ? 1 : 0,
            itemStyle: {
                areaColor: info.available ? '#BCDAFF' : '#D5DFED',
                borderColor: '#fff',
                borderWidth: 0.5,
            },
        }));
    };

    // 生成散点数据
    const generateScatterData = () => {
        return Object.entries(availableCountries.value)
            .filter(([_, info]) => info.available)
            .map(([code]) => ({
                name: code,
                value: countryCoordinates[code],
            }))
            .filter((item) => item.value);
    };

    // 初始化地图
    const initChart = () => {
        if (!chartRef.value) return;

        echarts.registerMap('world', worldLow, { nameProperty: 'id' });
        chartInstance = echarts.init(chartRef.value);

        const option = {
            geo: {
                map: 'world',
                roam: true,
                scaleLimit: { min: 0.8, max: 50 },
                zoom: 1.2,
                center: [0, 20],
                itemStyle: {
                    borderColor: '#fff',
                    borderWidth: 0.5,
                },
                regions: generateMapData(),
            },
            series: [
                {
                    type: 'effectScatter',
                    coordinateSystem: 'geo',
                    rippleEffect: {
                        brushType: 'stroke',
                        scale: 3,
                        period: 4,
                    },
                    symbol: 'circle',
                    symbolSize: 12,
                    itemStyle: {
                        color: '#1890ff',
                        shadowBlur: 10,
                        shadowColor: 'rgba(24, 144, 255, 0.5)',
                    },
                    data: generateScatterData(),
                    z: 1,
                },
            ],
        };

        chartInstance.setOption(option);
        window.addEventListener('resize', handleResize);
    };

    const handleResize = () => {
        chartInstance?.resize();
    };

    onMounted(() => {
        initChart();
    });

    onUnmounted(() => {
        if (chartInstance) {
            chartInstance.dispose();
            chartInstance = null;
        }
        window.removeEventListener('resize', handleResize);
    });
</script>

<style scoped>
    .map-wrapper {
        width: 100%;
        height: 600px;
    }

    .chart-container {
        width: 100%;
        height: 100%;
    }
</style>

十一、总结

本文详细介绍了使用 ECharts 实现世界地图的完整流程,包括:

地图初始化 - 注册地图数据、创建实例 ✅ 区域渲染 - 差异化显示不同状态的国家 ✅ 散点特效 - 使用

effectScatter 实现涟漪动画 ✅ 交互功能 - 点击、悬停、聚焦等交互 ✅ 性能优化 - 数据优化、事件节流、按需加载 ✅
问题解决 - 常见问题的解决方案

核心要点回顾

  1. 数据格式

    • 地图区域数据:{ name: territoryCode, value: number, itemStyle: {...} }
    • 散点数据:{ name: string, value: [经度, 纬度] }
  2. 层级关系

    • 使用 zlevelz 控制图层顺序
    • 散点图的 z 值应大于地图区域
  3. 坐标系统

    • 散点图必须指定 coordinateSystem: 'geo'
    • 坐标格式:[经度, 纬度]
  4. 性能优化

    • 使用低精度地图数据
    • 事件处理使用节流
    • 按需加载地图数据

扩展阅读

相关推荐
程序员小寒2 小时前
JavaScript设计模式(十):模板方法模式实现与应用
前端·javascript·设计模式·模板方法模式
Bigger2 小时前
第六章:我是如何剖析 Claude Code 的终端界面渲染原理的
前端·react.js·claude
We་ct2 小时前
EventSource & WebSocket & HTTP
前端·javascript·网络·websocket·网络协议·http·面试
张风捷特烈2 小时前
GetX 之死 | 8 年从未用过,以后将不会再用
android·前端·flutter
冲浪中台2 小时前
20个常用的CSS知识点
前端·css
青枣八神2 小时前
如何让手机访问电脑本地的前端服务器网页(Vite等前端项目)
服务器·前端·web·手机访问
榴莲omega2 小时前
第14天:React 工程化与设计模式
前端·react.js·设计模式
FmZero2 小时前
后端全栈路线(9小时前端速成)
前端·vscode·学习
万世浮华戏骨2 小时前
Web 后端 Python 基础安全
前端·python·安全