在数据可视化中,地图是很重要的一个环节,很多时候需要展现的不仅是国家地图,还需要能从国家进入到省市。这个逐级进入的过程就是我们今天说的地图下钻。
地图下钻看起来很屌、很高大上,但是仔细琢磨一下,技术实现上真的很简单。
文章目录
- 1. 本章主要功能介绍
- 1. 地图下钻实现思路(本章核心)
- 2. 下钻代码实现(本章核心)
- 3. 完整代码
- 3.1 vue部分
- 3.2 mapData.js 部分
- 4. 接口数据
1. 本章主要功能介绍
- 自定义标注样式&自定义tooltip弹窗样式(上章内容) echarts 自定义标注样式&自定义tooltip弹窗样式
- 自定义地图贴图样式(上章内容)echarts 实现中国geo地图自定义贴图实例
- 下拉菜单选择切换地图省份(本章核心)
- 点击地图切换地图省份(本章核心)
- 进入全屏/退出全屏(其他)
1. 地图下钻实现思路(本章核心)
mounted
里面首先初始化一个中国地图的数据,provinceCode默认为100000
- 创建echarts实例,获取中国地图的
geoJSON
数据 - 拿到geoJSON 数据调用
initEcharts
函数区创建echarts - geoJSON 数据对
echarts
进行渲染上图 - 上图完成后,通过
this.myChart.on("click",()=>{})
事件活动点击的对象,params.name
可以拿到点击的省份名称,修改provinceCode
值为点击的省份 - 监听
provinceCode
改变,重新执行第一递归循环即可
2. 下钻代码实现(本章核心)
3. 完整代码
3.1 vue部分
<template><div class="map"><div class="navProvince"><selectv-model="provinceCode"@change="changeProvince($event.target.value)"><option:value="item.code"v-for="(item, index) in provinceData()":key="index">{{ item.name }}</option></select><img src="@/assets/img/dataView/down.webp" alt="" srcset="" /></div><div class="navBase" v-if="baseId"><select v-model="baseId" @change="changeBaseId($event.target.value)"><option:value="item.baseId"v-for="(item, index) in baseData":key="index">{{ item.name }}</option></select><img src="@/assets/img/dataView/down.webp" alt="" srcset="" /></div><divclass="screen"@click="toggleFullScreen":title="isFullscreen ? '退出全屏' : '进入全屏'"><img src="@/assets/img/dataView/screen.webp" alt="" srcset="" /><span>{{ isFullscreen ? "退出全屏" : "进入全屏" }}</span></div><div ref="myEchart" id="personnel"></div></div>
</template><script>
import "echarts-gl"; //3D地图插件
import screenfull from "screenfull";
import axios from "axios";
import {provinceObj,provinceData,getCodeByName,formatHtml,
} from "./js/mapData";
import { API_listBase } from "@/api/dataView";
import { mapGetters } from "vuex";
export default {data() {return {provinceData,isFullscreen: false,publicUrl: "https://geo.datav.aliyun.com/areas_v3/bound/",chinaGeoJson: [],myChart: null,mapImg: null, //地图底色mapActiveImg: null, //地图悬浮移入后的底色baseData: [], //基地数据provinceCode: 100000,baseId: "",};},computed: {...mapGetters({getProvinceCode: "dataView/getProvinceCode",getBaseId: "dataView/getBaseId",}),},watch: {provinceCode(_newData, _oldData) {this.provinceCode = _newData;this.$store.commit("dataView/SET_PROVINCECODE",provinceObj[_newData] == "全国" ? "" : _newData);this.getList();},},methods: {// 请求三方地图数据接口async getGeoJson(jsonName, province) {let res = await axios.get(`${this.publicUrl}${jsonName}`);if (res && res.status === 200) {this.initEcharts(res.data, province);}},// 加载地图数据initEcharts(geoJson, name) {this.$echarts.registerMap(name, geoJson);let option = {tooltip: {trigger: "item",formatter: function (params) {// 为小图表创建一个容器if (params.data) {return params.name + " : " + params.data.data.datas;}},},geo: {map: name == "全国" ? "china" : name,zoom: 1,roam: false,itemStyle: {normal: {areaColor: {type: "radial",x: 0.5,y: 0.5,r: 0.8,colorStops: [{offset: 0,color: "#09132c", // 0% 处的颜色},{offset: 1,color: "#274d68", // 100% 处的颜色},],globalCoord: true, // 缺省为 false},shadowColor: "rgb(13, 48, 92,0.8)", //底层颜色shadowOffsetX: 10,shadowOffsetY: 11,},},regions: [{show: false,name: "南海诸岛",itemStyle: {areaColor: "rgba(0, 10, 52, 1)",borderColor: "rgba(0, 10, 52, 1)",normal: {opacity: 0,label: {show: false,color: "#009cc9",},},},},],},series: [{type: "map",roam: false,label: {normal: {show: true,textStyle: {color: "#D5E0EE",},},emphasis: {textStyle: {color: "rgb(183,185,14)",},},},itemStyle: {normal: {borderColor: "rgb(81, 184, 220)",borderWidth: 1,areaColor: {image: this.mapImg,repeat: "repeat",},},emphasis: {areaColor: {image: this.mapActiveImg,repeat: "repeat",},borderColor: "#2ab8ff",borderWidth: 1,shadowColor: "rgba(0, 255, 255, 0.7)",shadowBlur: 10,shadowOffsetX: 0,shadowOffsetY: 1,label: {show: false,},},},zoom: 1,map: name == "全国" ? "china" : name, //使用},{type: "scatter",coordinateSystem: "geo",itemStyle: {color: "#f00",},tooltip: {trigger: "item",backgroundColor: "transparent",formatter: function (params) {return formatHtml(params.data);},},symbol: `image://${require("@/assets/img/dataView/point.png")}`,symbolSize: [48, 58],symbolOffset: [0, 0],z: 9999,data: this.baseData,},],};this.myChart = this.$echarts.init(this.$refs.myEchart);this.myChart.setOption(option, true);this.myChart.off("click");this.myChart.on("click", (params) => {this.$store.commit("dataView/SET_BASEID", params.data?.baseId);if (params.data?.provinceCode) {this.baseId = params.data?.baseId;}if (params.data?.provinceCode || getCodeByName(params.name)) {this.provinceCode =params.data?.provinceCode || getCodeByName(params.name);} else {return this.$message.warning("暂无继续进行地图下钻");}});},// 切换全屏toggleFullScreen() {if (!screenfull.isEnabled) return false;screenfull.toggle();},changeProvince(_data) {this.baseId = "";this.$store.commit("dataView/SET_BASEID", "");},changeBaseId(_data) {let params = this.baseData.find((item) => item.baseId == _data);if (params) {this.provinceCode = params?.provinceCode;} else {return this.$message.warning("切换失败");}},async getList() {let { code, data } = await API_listBase({provinceCode: this.getProvinceCode,baseId: this.getBaseId,});if (code === 200) {this.baseData = data.map((item, _index) => {return {...item,value: item.baseLocation.split(","),name: item.unitName,province: item.province,computilityData: item.computilityData? item.computilityData: {baseCpuserverNumber: 0,baseCpuScores: 0,baseIdlecpuScores: 0,baseMemoryCapacity: 0,baseIdlememoryCapacity: 0,baseStorageCapacity: 0,baseIdlestorageCapacity: 0,baseUploadBandwidthCapacity: 0,baseIdleuploadBandwidthCapacity: 0,baseDownloadBandwidthCapacity: 0,baseIdledownloadBandwidthCapacity: 0,baseGpuserverNumber: 0,baseGpuScores: 0,baseIdlegpuScores: 0,baseNpuserverNumber: 0,baseNpuScores: 0,baseIdlenpuScores: 0,baseFp16Computility: 0,baseIdlefp16Computility: 0,baseFp32Computility: 0,baseIdlefp32Computility: 0,baseGraphicsMemoryCapacity: 0,baseIdlegraphicsMemoryCapacity: 0,},};});}// 初始化中国地图this.$nextTick(() => {this.myChart = this.$echarts.init(this.$refs.myEchart);this.getGeoJson(`${this.provinceCode}_full.json`,provinceObj[this.provinceCode]);});},},mounted() {// 地图底色this.mapImg = document.createElement("img");this.mapImg.style.height =this.mapImg.height =this.mapImg.width =this.mapImg.style.width ="100px";this.mapImg.src ="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAIAAAAmKNuZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMS1jMDAwIDc5LmRhYmFjYmIsIDIwMjEvMDQvMTQtMDA6Mzk6NDQgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCAyMy4wIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo3Qjk5OUJBRTZGNTMxMUVGODI5Q0EzOUY4MDEwOEUwNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo3Qjk5OUJBRjZGNTMxMUVGODI5Q0EzOUY4MDEwOEUwNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjdCOTk5QkFDNkY1MzExRUY4MjlDQTM5RjgwMTA4RTA3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjdCOTk5QkFENkY1MzExRUY4MjlDQTM5RjgwMTA4RTA3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+3sCwvQAAAdRJREFUeNqMVUtOAzEMta1uGKTuQD0K4iLdcy24QDlIz1KVHRJlZ+P87UwyZTat42+e7Rc8fnyezt/gPgFAIAA2on7hROIfzIactJAMjq97hLd3uP42n+eHKBa7wyNcfrLKaZO4wPVmRSqmxSFkrgm9ymmhpTQilSMx9Rt/7gO6j7ATu+pknRAsPrwRW1S7Cz+HJWOc4VvCoWZOpam2VkpJa2pPxiXHLrhdbq2D6nxx6DpRtV+3dgFnLLEV7EtmC6WEcmBwe4esx67q0ZSN4xbwBLVkzJrddQf7chg2P/FDpthxbQVm/4R9DaSjW9uS0MzrgRk+rlskthUWe2ld1rlHzHXcaxRNQPaoiYzu3jVKInY8CeFC4+TczEAUqV8DmkCOo7nJCHZjHOAs/MMRe1usFbO20BcZMYfTEMo5mSnEcE48CgRV6Uu1jyuCcuRWKEAsKeEKTWyc2s+dhL4XMyoWOIJ5+yQRFDbwCajvfXMUs8KTJem1UgkKGrpPi7taWoMazq5QR1+hFbDaiqt9HFJnoj/GTB1f2ZckboX8Y8lHD0OGFLupxc1xhy3q11s7+hOarMGQ8vDOABDuji/70xncI51eA1o/DtJekjbnrY0a6k+AAQAfPMeH5wCecwAAAABJRU5ErkJggg==";// 地图悬浮移入后的底色this.mapActiveImg = document.createElement("img");this.mapActiveImg.style.height =this.mapActiveImg.height =this.mapActiveImg.width =this.mapActiveImg.style.width ="100px";this.mapActiveImg.src ="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAIAAAAmKNuZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMS1jMDAwIDc5LmRhYmFjYmIsIDIwMjEvMDQvMTQtMDA6Mzk6NDQgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCAyMy4wIChXaW5kb3dzKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo3NjJDOUNEMDZGNTMxMUVGQjI2OUM4RUM4MzQwOTI5RiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo3NjJDOUNEMTZGNTMxMUVGQjI2OUM4RUM4MzQwOTI5RiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjc2MkM5Q0NFNkY1MzExRUZCMjY5QzhFQzgzNDA5MjlGIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjc2MkM5Q0NGNkY1MzExRUZCMjY5QzhFQzgzNDA5MjlGIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+LKM4xwAAAfVJREFUeNqEVV1OwzAMjqOqSKMPsN6AnYEdiCtxC47CzjBusJ+HDIkixThx6zpOO/LQxU7iON/32YOP97fwdXDTAHAY6cd5SGZE51CZMX29HzePq+lYmnS71yYcD+fvIW3K/qdNe80mj23Xnm+zaVafu/ZyGzhguuh48BQYxujjzehWh6yOX7XVj190MLlU4rO5OHgPIYDKkzDhYJygoANlXBNIT6B0NnSCAIr5HOPNJs8xmzpcbUb1lobgOIfBjQRm7MOAU2p9157yKggzYZCUe7WZifLmLRELHPVD/h2UmWewJF+NrsSFFfhcyWGCiDUJJcZQQY4V+/ULKJWGstPY12CTVnU6vVrFkjcKxVT8yk3bEl0yL2FQsR5O4aeomVCUkJeszXM0oMqDtbNgQ6iEdaFCCZMmB1Xo9Op7pbRCrkQksMBQQdgJ9iwag70xDVFJukq7DWV4vQ2CVF+i22cqdBlIkXDNUL8S6cxVgXcLoK52AQu1SKmjeLCBjIwXseOvh4KlhF1dK4uTxT3SzYSZoirQ2QZlsGe81toX+RsKa5RNYEOpe0G2LgOz2eqOVa2VabSNS11e/Ck7rXhG1xwDFR0UOXzZvBozs3d6DkBJNyy0vDk7Ynbzst8eP+cSj5XuH1u6Uv6hE1HTn7emkebdbv8nwADKuQThmsBu6AAAAABJRU5ErkJggg==";// 初始化中国地图this.getList();},
};
</script><style lang="less" scoped>
.map {flex: 1;width: 100%;color: #fff;position: relative;.navProvince,.navBase {position: absolute;left: 40px;width: 160px;height: 32px;display: flex;align-items: center;justify-content: space-between;background: url("../../../../assets/img/dataView/nav_bg.webp") no-repeat;background-size: 160px 32px;background-position: center;cursor: pointer;z-index: 2;img {height: 16px;position: absolute;top: 50%;right: 10px;transform: translateY(-50%);}}.navBase {left: 220px;}.screen {position: absolute;right: 40px;width: 118px;height: 32px;display: flex;align-items: center;justify-content: center;background: url("../../../../assets/img/dataView/screen_bg.webp") no-repeat;background-size: 118px 32px;background-position: center;cursor: pointer;z-index: 2;img {height: 24px;margin-right: 10px;}span {font-weight: normal;font-size: 16px;color: #4b94ff;line-height: 16px;}}
}#personnel {width: 100%;height: 100%;z-index: 1;
}
select {width: 100%;padding: 6px 10px 6px 16px;box-sizing: border-box;font-size: 16px;color: #4b94ff;line-height: 16px;-webkit-appearance: none;/* for Chrome, Safari */-moz-appearance: none;/* for Firefox */-ms-appearance: none;/* for IE10+ */appearance: none;-moz-appearance: none;background: transparent;-webkit-appearance: none;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);-webkit-tap-highlight-color: transparent;option {background: transparent;}
}
</style><style scoped lang="less">
/deep/ .tooltip-chart {background-color: transparent;width: 520px;height: 334px;background: url("../../../../assets/img/dataView/tooltip_bg.webp") no-repeat;background-size: 100% 100%;background-position: center;padding: 16px 25px 16px 20px;grid-gap: 0 40px;overflow: auto;display: grid;grid-template-columns: repeat(2, 1fr);.item {font-weight: 500;font-size: 13px;color: #ffffff;line-height: 20px;display: flex;justify-content: space-between;align-items: center;span {&.name{// color: #4b94ff;}&:first-child {position: relative;padding-left: 14px;&::after {content: "";position: absolute;top: 50%;left: 0;transform: translateY(-50%);width: 5px;height: 5px;border-radius: 50%;background: #fff;}}&:last-child {text-align: left;}}}
}
</style>
3.2 mapData.js 部分
export const provinceObj = {100000: "全国",110000: "北京",120000: "天津",130000: "河北",140000: "山西",150000: "内蒙古",210000: "辽宁",220000: "吉林",230000: "黑龙江",310000: "上海",320000: "江苏",330000: "浙江",340000: "安徽",350000: "福建",360000: "江西",370000: "山东",410000: "河南",420000: "湖北",430000: "湖南",440000: "广东",450000: "广西",460000: "海南",500000: "重庆",510000: "四川",520000: "贵州",530000: "云南",540000: "西藏",610000: "陕西",620000: "甘肃",630000: "青海",640000: "宁夏",650000: "新疆",710000: "台湾",810000: "香港",820000: "澳门",
};export let provinceData = () => {let arr = [];for (const key in provinceObj) {arr.push({code: key,name: provinceObj[key],});}return arr;
};/*** 根据名称查询对应的键* @param {*} name* @returns*/
export let getCodeByName = (name) => {for (const [key, value] of Object.entries(provinceObj)) {if (value === name) {return key;}}return null; // 如果没有找到
};/*** 基地展示数据* @param {*} param0* @returns*/
export let formatHtml = ({ name, computilityData }) => {return `<div class="tooltip-chart"><div class="item"><span>基地名称</span><span class="name">${name}</span></div><div class="item"><span>基地CPU服务器数量</span><span>${computilityData?.baseCpuserverNumber || 0}</span></div><div class="item"><span>基地CPU总核数</span><span>${computilityData?.baseCpuScores || 0}</span></div><div class="item"><span>基地空闲CPU核数</span><span>${computilityData?.baseIdlecpuScores || 0}</span></div><div class="item"><span>基地内存空间总容量</span><span>${computilityData?.baseMemoryCapacity || 0}</span></div><div class="item"><span>基地空闲内存空间容量</span><span>${computilityData?.baseIdlememoryCapacity || 0}</span></div><div class="item"><span>基地存储空间总容量</span><span>${computilityData?.baseStorageCapacity || 0}</span></div><div class="item"><span>基地空闲存储空间容量</span><span>${computilityData?.baseIdlestorageCapacity || 0}</span></div><div class="item"><span>基地上行网络带宽总量</span><span>${computilityData?.baseUploadBandwidthCapacity || 0}</span></div><div class="item"><span>基地空闲上行网络带宽</span><span>${computilityData?.baseIdleuploadBandwidthCapacity || 0}</span></div><div class="item"><span>基地下行网络带宽总量</span><span>${computilityData?.baseDownloadBandwidthCapacity || 0}</span></div><div class="item"><span>基地空闲下行网络带宽</span><span>${computilityData?.baseIdledownloadBandwidthCapacity || 0}</span></div><div class="item"><span>基地GPU服务器数量</span><span>${computilityData?.baseGpuserverNumber || 0}</span></div><div class="item"><span>基地GPU总核数</span><span>${computilityData?.baseGpuScores || 0}</span></div><div class="item"><span>基地空闲GPU核数</span><span>${computilityData?.baseIdlegpuScores || 0}</span></div><div class="item"><span>基地NPU服务器数量</span><span>${computilityData?.baseNpuServerNumber || 0}</span></div><div class="item"><span>基地NPU总核数</span><span>${computilityData?.baseNpuScores || 0}</span></div><div class="item"><span>基地空闲NPU核数</span><span>${computilityData?.baseIdlenpuScores || 0}</span></div><div class="item"><span>基地FP16总算力</span><span>${computilityData?.baseFp16Computility || 0}</span></div><div class="item"><span>基地空闲FP16算力</span><span>${computilityData?.baseIdlefp16Computility || 0}</span></div><div class="item"><span>基地FP32总算力</span><span>${computilityData?.baseFp32Computility || 0}</span></div><div class="item"><span>基地空闲FP32算力</span><span>${computilityData?.baseIdlefp32Computility || 0}</span></div><div class="item"><span>基地显存总量</span><span>${computilityData?.baseGraphicsMemoryCapacity || 0}</span></div><div class="item"><span>基地空闲显存</span><span>${computilityData?.baseIdlegraphicsMemoryCapacity || 0}</span></div></div>`;
};
4. 接口数据
- 把moke的接口数据也贴下,方便各位看代码