近期根据全新的需求重构一个老的项目,首页需要做一个立体的中国地图,原先的平面地图使用的是高德与echarts结合,地图用高德,点用echarts,而现在要做立体的地图,并且不需要世界地图的背景,于是我直接放弃了高德直接改全部由echarts来实现。
相关依赖
echarts 5.1.2
vue-echarts 6.0.0
lodash
element-ui
地图资源
采用高德的地图json
https://datav.aliyun.com/tools/atlas/index.html
基本地图显示
我绘制的立体地图的原理只是在页面上显示了两层的地图,简单来说就是底层使用暗色并且将中心点稍微下移,echarts的option配置如下
optionInner: {
animation: false, // 阻止拖拽和缩放时上下图层不同步
geo: [
{ // 底图
map: 'china', // 全国地图名称必须为china 不然无法显示南海的缩略图
id: 'down',
roam: true,
zoom: 1,
layoutCenter: ['50%', '51%'], // 地图位置
layoutSize: '100%',
itemStyle: {
borderColor: 'grey',
borderWidth: 0.5,
areaColor: 'black',
opacity: 0.5
},
emphasis: {
label: {
show: false
},
itemStyle: {
borderColor: 'grey',
borderWidth: 0.5,
areaColor: 'black',
opacity: 0.5
}
},
select: {
label: {
show: false
},
itemStyle: {
areaColor: 'black',
opacity: 0.5
}
},
regions: [
{
name: '南海诸岛',
itemStyle: {
opacity: 0
},
emphasis: {
itemStyle: {
opacity: 0
}
},
select: {
itemStyle: {
opacity: 0
}
}
}
],
z: 0
},
{
map: 'china',
id: 'up',
roam: true,
zoom: 1,
layoutCenter: ['50%', '50%'], // 地图位置
layoutSize: '100%',
label: {
show: true
},
itemStyle: {
areaColor: {
type: 'linear-gradient',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'steelblue' // 0% 处的颜色
}, {
offset: 1,
color: 'skyblue' // 50% 处的颜色
}]
},
},
zlevel: 1
}
],
series: [
{
name: '地图',
type: 'map',
geoIndex: 1,
data: []
}
]
}
注意点:
-
ehcarts的geo的map属性必须为'china',否则南海诸岛的缩略图不会显示!就这个问题折腾了我一整天,我一度想放弃echarts使用高德了,否则就政治不正确了。22/11/14更新:显示南海诸岛框只需要在json中添加南海诸岛对应的属性包括坐标就行。没有显示出来可能是map名称和注册时传入的map名称不对应。如需修改省份名称的位置,需要在json对应的省份/地区对象中添加cp属性,值为数组,即是label显示的位置。 - 为了展示效果不那么奇怪,隐藏底图的南海诸岛缩略图,需要在底图geo中设置南海诸岛的regions属性。
- 为了后面拖拽缩放时上下图层保持同步,需要设置animation属性为false,由于项目目前处于技术预研状态,所以我还不知道这个属性会不会影响其它的功能。
监听地图的缩放与拖动
通过监听georoam
事件来实现,需要特别注意的是,缩放与拖动需要调用echartsInstance.getOption()
来获取完整的option值并做出修改后覆盖原有的option,否则会出现bug。
// 监听地图缩放/拖动
handleGeoRoam(e) {
// console.log(e);
// getOption生成的对象包含了本身geo[1]里面没有的center和更新过的zoom,需要重新赋值
// chartInstance是通过ref保存的vue-echarts实例
const optionAll = _.cloneDeep(this.chartInstance.getOption());
let center, zoom;
if (e?.geoId === 'up') { // 正常在上层的地图上操作
if (e?.zoom) { // 处理缩放
zoom = optionAll.geo[1].zoom;
Object.assign(optionAll.geo[0], { zoom });
}
// 处理拖拽
center = optionAll.geo[1].center;
Object.assign(optionAll.geo[0], { center });
} else if (e?.geoId === 'down') { // 增加在底图上操作的判断
if (e?.zoom) {
zoom = optionAll.geo[0].zoom;
Object.assign(optionAll.geo[1], { zoom });
}
center = optionAll.geo[0].center;
Object.assign(optionAll.geo[1], { center });
}
this.optionInner = optionAll;
},
地图双击省份下钻以及点击按钮返回上一级
单击事件有时会无法生效故改成了双击下钻(估计可能和拖拽有冲突)
// 下钻(单击事件容易和拖拽冲突)
handleClick(e) {
// console.log(e);
if (e?.data?.code){
const { code, level, centroid } = e.data;
// 最后一级不可下钻
if (level !== 'country') {
return;
}
// 无数据的地区不可下钻
const noDataDistricts = [710000, 810000, 820000];
if (noDataDistricts.includes(code)) {
this.$message.warning('暂无相关数据');
return;
}
this.currentMapInfo = {
code,
level: 'province'
};
this.status.loading = true;
// 手动修改地图内容
const mapName = String(this.currentMapInfo.code);
const optionCopy = _.cloneDeep(this.optionInner);
this.updateMapGeoValue(optionCopy, 'map', mapName);
this.updateMapGeoValue(optionCopy, 'center', centroid);
this.updateMapGeoValue(optionCopy, 'zoom', 1);
this.optionInner = optionCopy;
// echarts注册地图方法无需赘述
this.registerMap(code);
// TODO:
// this.$emit('map-change', code);
this.setMapData(code);
this.status.loading = false;
}
},
// 地图返回上一级
handleMapBack() {
if (this.currentMapInfo.level === 'country') {
this.$message.warning('已经是全国地图了');
} else {
this.currentMapInfo = {
code: '100000',
level: 'country'
};
this.chartInstance.clear();
const optionCopy = _.cloneDeep(this.optionInner);
this.updateMapGeoValue(optionCopy, 'map', 'china');
this.updateMapGeoValue(optionCopy, 'zoom', 1);
this.updateMapGeoValue(optionCopy, 'center', [], 'delete');
this.optionInner = optionCopy;
this.setMapData();
}
},
/**
* @description: 同时设置两个geo的属性
* @param {object} option 深拷贝后的option
* @param {string} key option两个geo都有的key
* @param {any} value
* @param {string | undefined} type 'delete'为删除属性
* @return {void}
*/
updateMapGeoValue(option, key, value, type) {
option.geo.forEach(item => {
if (type === 'delete') {
delete item[key];
} else {
item[key] = value;
}
});
}
说明:
1.code,centroid,level是数据组装的时候放入data中的,前两者直接从地图文件中取到,level的值只有'country'和'province'代表全国和省份。
-
currentMapInfo
保存当前地图的code和等级。 -
setMapData
方法根据当前code获取对应的地图文件,并设置随机数据,code,centroid,level等地图信息,由于是模拟的业务逻辑也不赘述。
至此一个完整的立体地图就实现了。
参考内容
https://www.makeapie.cn/echarts_content/xr5cqmiBBf.html