地图+散点图效果:

react项目中安装echarts、echarts-gl依赖:
npm install echarts echarts-gl
文件目录结构:

地图组件map目录下文件代码,点击散点图圆点触发事件handleCityClick:
index.jsx:
javascript
import { useRef, useEffect, useState, useCallback } from "react";
import * as echarts from "echarts";
import 'echarts-gl';
import customSettings from "@config/customSettings";
import styles from "./style.less";
const { PROVINCE_NAME, MAP_NAME, MAP_JSON } = customSettings;
const ProvinceMap = ({
option,
onCityClick,
width = "100%",
height = "100%",
}) => {
const [selectedCity, setSelectedCity] = useState('');
const chartRef = useRef(null);
const mapInstanceRef = useRef(null); // 使用ref保存实例
const handleCityClick = useCallback((name) => {
console.log('点击参数:', name);
if (name === selectedCity) {
setSelectedCity(PROVINCE_NAME);
onCityClick(PROVINCE_NAME);
} else {
setSelectedCity(name);
onCityClick(name);
}
}, [selectedCity, onCityClick, PROVINCE_NAME]);
useEffect(() => {
if (!chartRef.current) return;
// 初始化图表
mapInstanceRef.current = echarts.init(chartRef.current);
echarts.registerMap(MAP_NAME, MAP_JSON);
// 设置选项
mapInstanceRef.current.setOption(option);
// 添加点击事件 - 3D地图需要特殊处理
const handleClick = (params) => {
console.log('完整点击参数:', params);
// 3D地图点击参数可能的结构
const cityName = params.name ||
(params.data && params.data.name) ||
(params.seriesName === 'map' && params.name);
if (cityName) {
handleCityClick(cityName);
}
};
mapInstanceRef.current.on('click', handleClick);
// 响应式调整
const resizeObserver = new ResizeObserver(() => {
mapInstanceRef.current?.resize();
});
resizeObserver.observe(chartRef.current.parentElement);
// 清理函数
return () => {
mapInstanceRef.current?.off('click', handleClick);
mapInstanceRef.current?.dispose();
resizeObserver.disconnect();
};
}, [option, MAP_NAME, MAP_JSON, handleCityClick]);
// 单独监听selectedCity变化时不重新渲染整个地图
useEffect(() => {
if (!mapInstanceRef.current) return;
// 更新选中状态而不重新初始化
mapInstanceRef.current.setOption({
series: [{
selectedMode: 'single',
select: {
itemStyle: {
color: '#FF4500' // 选中颜色
}
}
}]
}, true);
}, [selectedCity]);
return (
<div
ref={chartRef}
className={styles.mapClass}
style={{ width, height }}
/>
);
};
export default ProvinceMap;
如果想要点击地图区域触发点击事件可以将series的内容替换成下面的代码:
javascript
series: [
{
type: 'map3D',
map: customSettings.MAP_NAME,
regionHeight: 3, // 区域高度
itemStyle: {
color: '#1E90FF',
borderWidth: 1
},
emphasis: {
itemStyle: {
color: '#FFA500'
}
}
}
]
mapOption.js:
javascript
import customSettings from '@config/customSettings';
const backgroundColor = '#FFFFFF';
const borderColor = '#cbd1dc';
export const MAP_OPTION = {
tooltip: {
show: false,
backgroundColor,
borderColor, // 修改边框颜色
triggerOn: "mousemove", // 鼠标移动时触发
axisPointer: {
type: "none",
},
position: (point, params, dom, rect, size) => {
// 解决tooltip在最右侧时部分被遮挡
let obj = {};
if (point[0] > size.viewSize[0] / 2) {
// 鼠标位置位于echarts容器的一半位置右侧时,提示框显示在左侧
obj["left"] = point[0] - size.contentSize[0] - 20;
} else {
obj["right"] = size.viewSize[0] - size.contentSize[0] * 2;
}
if (point[1] > size.viewSize[1] / 2) {
// 鼠标位置位于echarts容器的一半位置下侧时,提示框显示在上侧
obj["top"] = point[1] - size.contentSize[1] - 20;
} else {
obj["bottom"] = size.viewSize[1] - size.contentSize[1] * 1;
}
return obj;
},
backgroundColor: "rgba(255,255,255,0.90)", // 提示标签背景颜色
textStyle: { color: "#fff" }, // 提示标签字体颜色
},
visualMap: {
show: false, // 是否显示 visualMap-continuous 组件。如果设置为 false,不会显示,但是数据映射的功能还存在
type: "continuous", // 类型为连续型视觉映射
calculable: false, // 是否显示拖拽用的手柄(手柄能拖拽调整选中范围)
inRange: {
// 定义在选中范围中的视觉元素
color: ["#ffd289", "#ff9c45"],
},
},
// 地理坐标系组件
geo3D: {
map: customSettings.MAP_NAME, // 地图名称,要和echarts注册的地图名称一致
// 添加交互
roam: false, // 设置为false,禁用地图的漫游功能
// 添加立体效果
boxHeight: 10, // 地图的高度,默认为0,即不显示立体效果
regionHeight: 3, // 地图上每个区域的立体高度,默认为0,即不显示立体效果
itemStyle: { // 地图区域的样式设置
color: '#1E90FF20',
opacity: 0.8,
borderWidth: 0.5,
borderColor: '#00FFFF'
},
emphasis: { // 鼠标悬停在地图区域上的样式设置
itemStyle: {
color: 'rgba(0, 153, 255, 0.3)'
},
label: { // 鼠标悬停在地图区域上显示的标签样式设置
show: true,
textStyle: {
color: '#fff',
fontSize: 12,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderRadius: 3,
padding: [4, 8]
}
}
},
light: { // 光照设置,用于增强立体效果
main: { // 主光源设置
intensity: 1.2, // 光源强度,默认为1.2
shadow: true, // 是否显示阴影,默认为true
shadowQuality: 'high', // 阴影质量,默认为'high'
alpha: 30, // 固定俯仰角度(上下)
beta: 40 // 固定方位角度(左右)
},
ambient: { // 环境光设置
intensity: 0.3 // 环境光强度,默认为0.3
}
},
viewControl: { // 视角控制设置,用于调整地图的旋转和缩放等操作
distance: 120, // 视角距离地图的距离,默认为120
alpha: 40, // 固定俯仰角度(上下)
beta: 0, // 固定方位角度(左右)
autoRotate: false, // 设置为false
rotateSensitivity: 0, // 禁用旋转
zoomSensitivity: 0, // 0:禁用缩放
}
},
series: [{
type: 'scatter3D',
coordinateSystem: 'geo3D',
symbolSize: 12,
encode: {
tooltip: 2 // 第三个值(value[2])显示在tooltip中
},
itemStyle: {
color: '#FF4500',
opacity: 0.8
},
emphasis: {
itemStyle: {
color: '#00FFFF',
borderWidth: 2,
borderColor: '#FFF'
},
label: {
show: true,
formatter: '{b}',
color: '#FFF',
backgroundColor: 'rgba(0,0,0,0.7)',
padding: [4, 8]
}
},
data: [
// 格式: { name: '地市', value: [经度, 纬度, 值] }
{ name: '郑州', value: [113.62, 34.75, 90] }, // 省会
{ name: '洛阳', value: [112.45, 34.62, 80] }, // 古都
{ name: '南阳', value: [112.53, 33.01, 75] }, // 豫西南
{ name: '新乡', value: [113.88, 35.30, 70] }, // 豫北
{ name: '商丘', value: [115.65, 34.45, 65] }, // 豫东
{ name: '安阳', value: [114.38, 36.10, 60] }, // 豫北
{ name: '开封', value: [114.31, 34.80, 55] }, // 古都
{ name: '焦作', value: [113.24, 35.22, 50] }, // 豫西北
{ name: '平顶山', value: [113.19, 33.77, 45] }, // 豫中
{ name: '信阳', value: [114.07, 32.13, 40] }, // 豫南
{ name: '周口', value: [114.65, 33.62, 35] }, // 豫东南
{ name: '驻马店', value: [114.02, 32.98, 30] }, // 豫南
{ name: '许昌', value: [113.81, 34.02, 25] }, // 豫中
{ name: '漯河', value: [114.02, 33.58, 20] }, // 豫中
{ name: '三门峡', value: [111.20, 34.78, 15] }, // 豫西
{ name: '鹤壁', value: [114.30, 35.75, 10] }, // 豫北
{ name: '濮阳', value: [115.03, 35.77, 8] }, // 豫东北
{ name: '济源', value: [112.60, 35.08, 5] } // 省直辖
]
}]
};
style.less:
css
.mapClass {
:global {
.custom-tooltip-box {
width: 306px;
overflow: auto;
color: #868686; // li文字颜色
font-size: 13px;
line-height: 22px;
font-weight: 400;
.title-text {
font-family: PingFangSC-Semibold;
font-size: 15px;
color: #262626;
text-align: center;
font-weight: 600;
padding-bottom: 3px;
border-bottom: 1px dashed #d9d9d9;
}
ul {
margin-top: 7px;
padding-left: 5px;
padding-right: 10px;
li {
list-style: none;
line-height: 1.5;
position: relative;
.value-box {
position: absolute;
display: inline-block;
right: 0;
.num {
color: #262626;
font-family: PingFangSC-Semibold;
font-size: 13px;
font-weight: 600;
}
}
}
}
}
}
}
引用地图组件的父组件index.jsx:
javascript
import React, { useState, useEffect } from 'react';
import { Progress, Table, Select, Drawer, Radio, DatePicker, Button, message, } from 'antd';
import { SettingOutlined } from '@ant-design/icons';
import styles from './style.less';
import ProvinceMap from './map/index';
import { MAP_OPTION } from './map/mapOption';
import { } from './service';
import customSettings from '@config/customSettings';
const { PROVINCE_NAME, CAPITAL_AREA_NAME } = customSettings;
export default function index() {
const [mapIndexsObj, setMapIndexsObj] = useState([]); // 省地市指标值
const [mapOption, setMapOption] = useState({}); // 地图option
const [areaName, setAreaName] = useState(CAPITAL_AREA_NAME); // 地市名称
// 获取地图option
useEffect(() => {
let option = { ...MAP_OPTION };
// 鼠标移到图里面的浮动提示框
option.tooltip.formatter = (params) => {
const { name } = params;
let shortName = '';
if (PROVINCE_NAME.includes('内蒙')) {
shortName = name.slice(0, name.length - 1);
} else {
shortName = name;
}
let tooltipHtml = `
<div class="custom-tooltip-box">
<div class="title-text">
${shortName}
</div>
<ul>`;
// 使用 map 函数遍历数据并构建 HTML 字符串
tooltipHtml += mapIndexsObj[shortName]?.map((item, index) => `
<li>
<span>${item.label}</span>
<div class="value-box">
<span class="num">${(item.value != undefined && item.value != null) ? item.value : '--'}</span>
<span class="unit" style="display: ${(item.value != undefined && item.value != null) ? 'inline' : 'none'}">${item.unit}</span>
</div>
</li>
`).join('') || ''; // .join('')去除map返回结果中的逗号
tooltipHtml += `</ul></div>`
return tooltipHtml;
};
setMapOption(option);
}, [mapIndexsObj]);
// 点击地图获取地市名称
const handleCityClick = (cityName) => {
setAreaName(cityName);
};
return (
<div className={styles.container}>
<ProvinceMap option={mapOption} onCityClick={handleCityClick} />
</div>
)
}
配置项文件customSettings:
javascript
// 用于设置地图、查询条件中"地市"下拉菜单的初始值
import henanMap from "@/assets/map/henan.json";
export default {
PROVINCE_ID: 37, // 与接口返回的类型保持一致
PROVINCE_NAME: '河南省',
CAPITAL_AREA_ID: 371, // 与接口返回的类型保持一致
CAPITAL_AREA_NAME: '郑州市',
MAP_JSON: henanMap,
MAP_NAME: 'henan',
};