地图下钻是一个非常常见的功能需求,本篇文章会细致讲解如何在Vue3中使用Eharts-gl渲染出3D地图,并且实现地图下钻和返回上级地图的完整功能。
github项目demo地址:点击这里
注意此项目为vue3版本,vue2版本在仓库分支里
给个星星吧!!不定期更新此demo,一般只更新vue3的版本,2版本自行迁移即可。
demo依赖版本确认:
"axios": "^1.3.5",
"echarts": "^5.2.2",
"echarts-gl": "^2.0.9",
"vue": "^3.2.47"
我把需求拆分成两个主要功能模块:初始化地图和更新地图功能构思:
initmap
初始化地图 -> getMapJSON
获取地图JSON并且初始化地图配置项data数据 -> getOption
获得地图总配置项 -> updateMap
更新配置项updateMap
backMap
返回上级地图updateMap函数其实就是echarts.setOption,把它抽离成单独函数是为了灵活更新配置项,当我们修改配置线例如请求接口返回新配置后,调用updateMap去更新地图
setIntervalOptionsRegionsMap
高亮区域动画点击区域:初始化地图 => 给地图添加点击事件 => 拿到用户点击的区域名称 => 保存用户点击的区域做历史记录 => 请求接口获取到该区域的JSON数据 => 重新渲染
点击返回图标: 遍历历史记录 => 弹出当前历史记录的地图信息 => 找到要返回上一级的地图信息 => 重新渲染地图
自动高亮展示区域: setInterval
定时器修改配置项 => 更新配置项 => 渲染新配置项
关于使用用户内存保存历史记录,还是请求接口保存的问题:
1.地图下钻和返回上级功能
2.地图自动高亮显示区块功能
<template>
<div class="investment-screen">
<svg
style="position: absolute; left: 20px; top: 20px; cursor: pointer"
@click="backMap"
t="1681180771137"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="3427"
width="200"
height="200"
>
<path
d="M426.666667 384V213.333333l-298.666667 298.666667 298.666667 298.666667v-174.933334c213.333333 0 362.666667 68.266667 469.333333 217.6-42.666667-213.333333-170.666667-426.666667-469.333333-469.333333z"
p-id="3428"
fill="#ffffff"
></path>
</svg>
<div class="map-chart" id="mapEchart"></div>
</div>
</template>
<script lang="ts" setup>
import * as echarts from "echarts";
import "echarts-gl"; //3D地图插件
import { onMounted, ref } from "vue";
import axios from "axios";
/**
* 初始化地图
*/
// 定义echarts方法
const chartMap = async () => {
// 初始化dom
const myChart = echarts.init(
<HTMLElement>document.getElementById("mapEchart")
);
// 初始化map
initMap(myChart, "map", "100000");
// 添加点击事件
myChart.on("click", (e: any) => {
clearInterval(regionsSetInterVal.value);
console.log(e);
const newName: string = e.name;
if (e.value.level === "district") return alert("该地区已经无法下钻");
// 添加历史记录
historyMapData.value.push(e.value);
// 初始化地图
initMap(myChart, newName, e.value.adcode);
});
// 添加鼠标移入事件
myChart.on("mouseover", (e: any) => {
console.log("鼠标移入");
clearInterval(regionsSetInterVal.value);
});
// 添加鼠标移出事件
myChart.on("mouseout", (e: any) => {
console.log("鼠标移出");
});
//让可视化地图跟随浏览器大小缩放
window.addEventListener("resize", () => {
myChart.resize();
});
};
// 初始化图表
const initMap = async (
chartDOM: echarts.ECharts,
geoName: string,
adcode: string
) => {
// 清除echarts实例
chartDOM.clear();
// 请求map的json
const mapData = await getMapJSON(adcode, geoName);
// 图表配置项
const option = getOption(geoName, mapData);
// 渲染配置
setIntervalOptionsRegionsMap(option, mapData, chartDOM);
// updateMap(chartDOM, option);
};
/**
* 地图配置项
*/
// 请求地图json数据,并过滤成地图data配置项
const getMapJSON = async (adcode: string = "100000", geoName: string) => {
const res = await axios.get(
`https://geo.datav.aliyun.com/areas_v2/bound/${adcode}_full.json`
);
// 重新注册地图
echarts.registerMap(geoName, <any>res.data);
// 过滤json数据
const lightMap: any = {
河南省: {
show: true,
formatter: (e: any) => {
return ` ${e.name} `;
},
textStyle: {
color: "#f8fbfb",
fontSize: 18,
padding: [20, 20],
backgroundColor: {
image: "./2.png",
},
},
},
郑州市: {
show: true,
formatter: (e: any) => {
return ` ${e.name} `;
},
textStyle: {
color: "#f8fbfb",
fontSize: 18,
padding: [20, 20],
backgroundColor: {
image: "./2.png",
},
},
},
};
const mapData = res.data.features.map((item: any) => {
console.log(item.properties.name);
return {
value: item.properties,
name: item.properties.name,
label: lightMap[item.properties.name],
};
});
return mapData;
};
// 图表生成配置项
const getOption = (geoName: string, mapData: any) => {
// 图表配置项
const option = {
geo3D: {
zlevel: -100,
show: true,
type: "map3D",
map: geoName, // 地图类型。echarts-gl 中使用的地图类型同 geo 组件相同
regionHeight: 2,
shading: "realistic",
realisticMaterial: {
detailTexture: "./1.jpeg",
roughness: 0.2,
metalness: 0,
},
// viewControl: {
// minAlpha: 70, // 上下旋转的最小 alpha 值。即视角能旋转到达最上面的角度。[ default: 5 ]
// maxAlpha: 90, // 上下旋转的最大 alpha 值。即视角能旋转到达最下面的角度。[ default: 90 ]
// minBeta: -360, // 左右旋转的最小 beta 值。即视角能旋转到达最左的角度。[ default: -80 ]
// maxBeta: 360, // 左右旋转的最大 beta 值。即视角能旋转到达最右的角度。[ default: 80 ]
// },
regions: [
{
name: mapData[0].name,
// label: {
// show: true,
// textStyle: {
// color: "#fff", // 地图初始化区域字体颜色
// fontSize: 18,
// },
// },
itemStyle: {
color: "#ff9900",
},
},
], //默认高亮区域
emphasis: {
label: { show: false },
itemStyle: {
color: "transparent",
},
},
},
series: [
{
zlevel: -10,
regionHeight: 2,
type: "map3D",
map: geoName, // 地图类型。echarts-gl 中使用的地图类型同 geo 组件相同
data: mapData, //这里比较重要:获得过滤后的data,这样点击事件时就能获得这个data的值
label: {
show: true, // 是否显示标签。
textStyle: {
color: "#fff", // 地图初始化区域字体颜色
fontSize: 12,
},
formatter: (e: any) => {
// console.log(e.name);
return ` ${e.name} `;
},
},
shading: "realistic",
realisticMaterial: {
detailTexture: "./4.jpeg",
roughness: 0.2,
metalness: 0,
},
// viewControl: {
// minAlpha: 70, // 上下旋转的最小 alpha 值。即视角能旋转到达最上面的角度。[ default: 5 ]
// maxAlpha: 90, // 上下旋转的最大 alpha 值。即视角能旋转到达最下面的角度。[ default: 90 ]
// minBeta: -360, // 左右旋转的最小 beta 值。即视角能旋转到达最左的角度。[ default: -80 ]
// maxBeta: 360, // 左右旋转的最大 beta 值。即视角能旋转到达最右的角度。[ default: 80 ]
// },
itemStyle: {
borderWidth: 1.5,
borderColor: "#5FB9DA",
color: "transparent",
},
emphasis: {
label: {
show: true,
textStyle: {
color: "#f8fbfb",
// borderColor: "#17E8F4",
fontSize: 18,
padding: [20, 20],
backgroundColor: {
image: "./2.png",
},
},
},
itemStyle: {
color: "#18B6FE",
},
},
},
],
};
return option;
};
/**
* 更新地图功能
*/
// 更新图表配置项重新渲染
const updateMap = (chartDOM: echarts.ECharts, option: any) => {
// 渲染配置
chartDOM.setOption(option);
};
/**
* 返回上级地图功能
*/
type HistoryData = {
name: string;
adcode: string | undefined;
};
// 地图下钻历史记录
const historyMapData = ref<HistoryData[]>([{ name: "map", adcode: "100000" }]);
// 返回上级地图
const backMap = () => {
clearInterval(regionsSetInterVal.value);
const myChart = echarts.init(
<HTMLElement>document.getElementById("mapEchart")
);
// 去除当前的地图信息
historyMapData.value.pop();
const len = historyMapData.value.length;
// 获取上一级的地图信息
const newdata = historyMapData.value[len - 1];
// 重新渲染地图
initMap(myChart, newdata?.name || "map", newdata?.adcode || "100000");
};
/**
* 高亮区块功能
*/
// 轮训 regions 地图名的下标
let regionsCount = ref<number>(0);
// 定时器接收容器
const regionsSetInterVal = ref();
// 循环定时器修改地图option高亮显示地图区域
const setIntervalOptionsRegionsMap = (
option: any,
mapData: any,
chartDOM: echarts.ECharts
) => {
regionsSetInterVal.value = setInterval(() => {
option.geo3D.regions[0].name = mapData[regionsCount.value].name;
updateMap(chartDOM, option);
regionsCount.value++;
if (regionsCount.value === mapData.length) regionsCount.value = 0;
}, 1000);
};
/**
* 生命周期
*/
onMounted(() => {
// 挂载echart
chartMap();
});
</script>
<style scoped>
.investment-screen {
background-color: rgb(0, 0, 42);
width: 100vw;
height: 100vh;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.map-chart {
width: 80%;
height: 80%;
/* background-color: wheat; */
}
</style>
getOption
函数中,使用了双地图geo3D
和map3D
两种类型,因为map3D
更友好的支持点击事件,所以为了实现点击下钻的功能,我把map3D
的视图层级往上提升了,并且透明化了map3D
每一块区域的颜色map3D单独配置时:
这么做就是为了完成:“鼠标移入相关地区区域时高亮展示红框立体区域内容”的需求
三维图形的着色效果
realistic
真实感渲染
在使用自定义渲染时,材质贴图一定要防抖 pubic文件夹 中,否则echarts无法识别。应该是底层echarts打包的时候自己做的处理。
shading: "realistic",
realisticMaterial: {
detailTexture: "./1.jpeg",
roughness: 0.2,
metalness: 0,
},
配置鼠标移入时的图标,可以自定义更换 image: "./2.png"
emphasis: {
label: {
show: true,
textStyle: {
color: "#f8fbfb",
// borderColor: "#17E8F4",
fontSize: 18,
padding: [20, 20],
backgroundColor: {
image: "./2.png",
},
},
},
itemStyle: {
color: "#18B6FE",
},
},
本文中地图下钻和返回上一级地图的整体功能需求基本完善。
高亮显示区域这个功能,会阻碍鼠标移入时的样式展示,所以每次鼠标移入时,都要清除regionsSetInterVal
这个定时器,那么导致定时器清除动画不会播放。
解决方案:鼠标移出时应该重新开始这个计时器,并且要添加防抖,提升性能。(待完成)
添加动态3d柱状图
添加动态3d散点图
添加动态3d折线图
。。。等等功能待开发