🌏 概论
相信每位开发大屏的小伙伴,都或多或少接触过这样一个需求:基于省、市、区三级下钻地图的可视化数据展示。地图上的数据展示对于大家来说基本都没有什么难度,但三级下钻地图的开发着实要花费一番心思。
为了避免重复造轮子,自己也前前后后开发、优化了好几版下钻地图的代码,但终究感觉不尽人意。直到最近利用Proxy
的监听机制进行了重构,补上了一部分之前体验上的缺陷,才算是完成了一份较为满意的答卷。
下面就和大家分享下开发思路,欢迎交流~
🌐源码地址:三级下钻地图源码
🌐演示地址:三级下钻地图Demo
有帮助的话还请给个star⭐~
🌏 基本思路
熟悉echarts Geo地图开发的小伙伴应该都清楚,Echarts每次地图的切换,其实都是重新注册一份静态的geo文件作为底图,而每张地图也都对应这一个唯一的编码:adcode
。
那我们便可以将adcode
作为地区切换功能的核心监听对象:
(1)全局注册Watcher
对象监听adcode
变化;
(2)地图切换(下钻、返回)时触发Watcher
对象中set
钩子;
(3)set
钩子中判断、更新adcode
,同步获取geo资源;
(4)Echarts中注册新geo,完成地图切换。
🌏 核心代码
我比较习惯直接在注释里说明代码思路,这里就不多言了。
/*
* @Author: AnZhou
* @FileName: main.js
* @Description: 入口文件
*/
import cityMapByName from '../assets/json/cityMapByNameSimple.json';
import cityMapByCode from '../assets/json/cityMapByCodeSimple.json';
import STATIC_DATA from '../data/staticData';
import mapOption from '../data/mapOption';
import getJson from '../request/getJson';
import judgeArea from '../utils/judgeArea';
let mapIns = null; //echarts实例
let mapDom = document.getElementById('chart-map'); //地图容器
let backBtnDom = document.getElementById('back-btn'); //返回父级按钮
let prevAdcode = null; //父级adcode
let areaInfo = {}; //当前区域的具体信息,详见../utils/judgeArea
/**
* tips--start
* 如下三项name在此demo中无用,但实际中很可能会遇到,故保留
* 如果你的项目中不需要,可自行删除相应代码
/** tips--end */
let provName = null; //当前区域所在的省份全称,如无则为null
let cityName = null; //当前区域所在的省份全称,如无则为null
let districtName = null; //当前区域所在的省份全称,如无则为null
//核心监听对象
let watcher = new Proxy(
{
adcode: null, //当前区域的adcode
},
{
get(target, key) {
return target[key];
},
set(target, key, value) {
target[key] = value;
// 获取当前区域的具体信息
areaInfo = judgeArea(watcher.adcode);
//获取省市区三级全称(如需)
getNames();
mapController();
backBtnController();
return true;
},
}
);
//控制地图生成
function mapController() {
/**
* tips--start
* 1. 仅当地图等级为国家级、省级、市级时可点击下钻
* 2. 直辖市、澳门、香港、台湾较为特殊,只展示两级
/** tips--end */
if ([0, 1].includes(areaInfo.districtRank)) {
//国家、省级时
prevAdcode = STATIC_DATA.CHINA_ADCODE;
pipeLine();
} else if ([2, 3].includes(areaInfo.districtRank)) {
//当前区域为市、区级时
if (areaInfo.isSpecialArea) {
//特殊地区的父级为中国
prevAdcode = STATIC_DATA.CHINA_ADCODE;
} else {
//其余父级为所在省、自治区
prevAdcode = areaInfo.provAdcode;
}
//仅点击市级可下钻
areaInfo.districtRank == 2 && pipeLine();
}
}
//控制返回按钮样式
function backBtnController() {
backBtnDom.style.display = areaInfo.districtRank == 0 ? 'none' : 'inline-flex';
}
// 通过cityMapByCode中的json查询相应地区的省、市、区名称
function getNames() {
/**
* tips--start
* 如果你的项目中不需要省市县全称,请可自行删除以下代码
* 并删除顶部引用的cityMapByCode静态json
*/
/** tips--end */
[provName, cityName, districtName] = [
areaInfo.provAdcode ? cityMapByCode[areaInfo.provAdcode] : '',
areaInfo.cityAdcode ? cityMapByCode[areaInfo.cityAdcode] : '',
areaInfo.districtAdcode ? cityMapByCode[areaInfo.districtAdcode] : '',
];
}
//注册生成echarts地图、绑定点击事件
function initMapChart({ mapDom, mapJson, mapOption }) {
if (!mapIns) {
mapIns = echarts.init(mapDom);
mapIns.off('click');
mapIns.on('click', async params => {
watcher.adcode = cityMapByName[params.name];
});
}
echarts.registerMap('统计地图', mapJson);
mapIns.setOption(mapOption);
}
//总线
async function pipeLine() {
//异步获取geojson
const mapJson = await getJson(watcher.adcode);
initMapChart({ mapDom, mapJson, mapOption });
}
//设置容器样式
mapDom.style.width = '100%';
mapDom.style.height = window.innerHeight - 56 * 2 + 'px';
//绑定返回事件
backBtnDom.addEventListener('click', () => {
watcher.adcode = prevAdcode;
});
//初始化地图
watcher.adcode = '100000';
/*
* @Author: AnZhou
* @FileName: judgeArea.js
* @Description: 地区信息计算函数
*/
import STATIC_DATA from '../data/staticData';
let [provAdcode, cityAdcode, districtAdcode, districtRank, isSpecialArea] = [
...Array(4).fill(null),
false,
];
function judgeArea(adcode) {
/*
* 根据国家标准《中华人民共和国行政区划代码》
* 标准号GB/T 2260-2007
* adcode代码由六位组成
* 前两位表省、自治区级
* 中两位表市州级
* 后两位表区县级
*/
adcode = adcode + '';
let result = null;
if (adcode === '100000') {
//展示地图为中国
result = [...Array(3).fill(null), 0, false];
} else if (/^\d{2}0{4}$/.test(adcode)) {
//展示地图为省、自治区级
if (STATIC_DATA.SPECIAL_AREA.includes(adcode)) {
//省级-特殊地区
result = [adcode, adcode, null, 1, true];
} else {
//省
result = [adcode, null, null, 1, false];
}
} else if (/^\d{4}0{2}$/.test(adcode)) {
//展示地图为市州级
result = [adcode.substring(0, 2) + '0000', adcode, null, 2, isSpecialArea];
} else {
//展示地图(选中区域为)区县级
result = [
adcode.substring(0, 2) + '0000',
adcode.substring(0, 4) + '00',
adcode,
3,
isSpecialArea,
];
}
[provAdcode, cityAdcode, districtAdcode, districtRank, isSpecialArea] = result;
return {
provAdcode, //省级adcode
cityAdcode, //市级adcode
districtAdcode, //区级adcode
districtRank, //当前地图等级:0-国 1-省 2-市 3-区
isSpecialArea, //是否特殊地区
};
}
export default judgeArea;
🌏 其余代码
受限于篇幅非核心代码就不贴上来了,感兴趣可以去Github翻看。
🌏 额外说明
🚩 1. Geojson文件从哪来?
(1)阿里DataV的geojson开放平台。(本demo是获取其所有静态文件后,存入了git仓库(点击查看),然后再利用jsdelivr
CDN返回);
(2)巧妙利用高德开放平台的行政区域查询API(这个方法我没试过,大家自己了解下)。
🚩 2. 为什么我的地图切换时没有过渡效果?
Echarts4.3以上版本都没有地图切换的过渡效果,如果想用过渡效果务必使用4.3以下版本(当然也不要太老)。
另补一句,过渡效果有望在5.3版本回归,详见issue。
🚩 3. 有没有Vue/React版本?
目前没有,不过迁移相信对大家来说不是问题。
🚩 4. 为什么我修改了源码后页面上看不到效果?
本Demo中使用了Rollup,每次更改源码后必须重新打包才可。至于为什么不能热更新,因为rollup的热更新插件安装老是报错,就没有加这个功能。反正正式项目中基本都不会用Rollup,你说对吧😀。
🚩 4. 这代码不行,我有更好的!
欢迎交流🥳🥳~