最近在工作项目中遇到一个绘制地图轮廓,并且在地图上根据需求里的学校位置打点,从最初的无从下手到最后完成,中间是边看echarts([echarts官网文档][1])文档边尝试,开了一个好头后面越写越顺利,因此谨以此篇文章记录下我本次学习的过程。
首先上图看效果
然后说说地图的实现思路:
- 首先需要绘制出地图轮廓;
- 请求接口数据,然后在地图上打点;
- 地图上的学校点击时异步加载学校数据
现在开始说正经的:
第一步:绘制地图轮廓
在绘制地图的准备工作中,需要事先准备好需要绘制的地区的json文件(主要是区域轮廓图的一些重要拐点经纬度,地图就是依靠它一个点一个点连线绘制出来滴),大概长这样:
然后在代码导入并中注册:
import echarts from 'echarts'
var config = require('../../../../static/mock/conf')
var dingnan = require(`../../../../static/mock/${config.map}`)
echarts.registerMap('dingnan', dingnan)
前两句代码基本上可以合并成一句,其意思是说给这个地图Json起了个名字,引入的时候要对号入座(还有其他地域地图)。。。
这一步完成之后,相当于地图已经注册了,但是需要一个载体来呈现它,这时候echarts就出场了:
各种数据初始化准备:
另外我封装了一个方法makeMapOption用来装载echarts图表所需的各种参数(主要为了方便后面接口请求数据后传到这个方法中地图打点):
当程序解析到
mounted() {
this.mapChart = this.$refs.map.chart
this.mapOption = makeMapOption()
},
的时候,地图轮廓已经加载出来了:
第二步:地图打点
这里就需要异步请求接口了,根据后台返回的数据看看该在哪里打点,打点最重要的两个参数就是经度和纬度啦,接口返回的数据大概长这样:
这个latitude和longitude就是每个学校的经纬度了,而schoolCode则是我们在第三步需要用到的重要参数,接下来就是拿着这些数据封装成我们地图中所需要的结构:
分别是打点的学校的名字,学校id,经纬度,学校类别...
之后传到刚才说的方法中去,echaerts图表参数主要部分series中其中一个项大概就是这样滴(这个截图对应的是文章开头第一张图中的黄色点学校,该地区高中中职在我们系统中的就这两...,同理小学初中,完全中学,教育局也是一样滴,不同就是经纬度和颜色啦,看到这里如果懵了的话,不要捉急,代码被我截图拆的四零八散,最后我会附上完整代码滴~~~~):
到这地图打点算是完成了,效果就是图一的样子,但是只有地图和点,没有其他信息,看官也不知道这些点到底是干嘛滴不是吗,所以再看
第三步:异步获取地图上某个点的信息
其实本来是很简单的一个echarts图表中的tooltip,就是像echarts官网中的许多例子一样,鼠标放上去出现一个小tips,有什么系统名啊,本item的值啊,百分比啊云云,but我们的需求不按常理出牌啊,tips上的文字是自定义滴,需要展示的数据还是需要请求滴,一开始我还是不会滴...
就是图上红框框里那个玩意儿~~
于是我又去看了看echarts官网关于formatter链接描述的部分,
划重点aaaaaa
第二个参数 ticket 是异步回调标识,配合第三个参数 callback 使用。 第三个参数 callback 是异步回调,在提示框浮层内容是异步获取的时候,可以通过 callback 传入上述的 ticket 和 html 更新提示框浮层内容。
(感谢官网大大)
然后怎么办?--硬着头皮写啊,异步请求啊,连接字符串啊,显示啊,上代码:
tooltip: {
show: true,
triggerOn: 'click',
backgroundColor: 'rgba(10, 17, 64, 0.8)',
formatter(value, ticket, callback) {
let _this = this
fetchSchoolDeatail({ unitCode: value.data.schoolCode }).then(({ data, headers }) => {
let info = data.data
let str = `<div style="padding: 10px;">
<h4>${value.name}</h4>
<p>${info.bxTypeName}</p>
<p style="display: flex; line-height: 200%">
<span style="flex: 1 1 50%;margin-right: 30px;">在册学生<br/><b style="color: #62b2e2; font-size: 200%">${info.studentNum}</b>人 </span>
<span style="flex: 1 1 50%;">在册职工数<br/><b style="color: #65db8a; font-size: 200%">${info.teacherNum}</b>人</span>
</p>
<p style="display: flex; line-height: 200%">
<span style="flex: 1 1 50%;margin-right: 30px;">今日到校学生数<br/><b style="color: #62b2e2; font-size: 200%;">${info.studentArriveSchoolNum}</b> 人</span>
<span style="flex: 1 1 50%;">今日到校职工数<br/><b style="color: #65db8a; font-size: 200%">${info.teacherArriveSchoolNum}</b>人</span>
</p>
</div>`
callback(ticket, str)
}).catch(err => {
console.dir(err)
_this.$message.error(err.message || '获取学校信息失败,请稍后再试')
}).finally(() => {
console.log(1)
})
return '加载中...'
}
},
有几点说明:
- 为了避免鼠标划过地图点就请求接口导致接口卡顿,触发tooltip的方法改成了click,也就是点击一下学校点才会加载学校信息接口数据;
- 由于接口请求发送到成功返回需要一点时间,刚一点上去可能是空白或者tips没出来,所以点击那一瞬间tips显示的是“加载中...”,这就是最外层的return;
- 当数据加载完成之后必须要写callback(ticket, str),这是人家formatter回调函数规定滴,ticket异步标识不能改,否则不返回任何东西。
至此呢,整个地图绘制,打点,加载点信息就完成了,下面补充几个我在写代码时的注意项:
- 绘制图表的点的样式可以是一般的ciecle圆点或者rect方形,也可以自定义,比如我地图中的教育局图表,就是引入了一张图片
const starImg = require('../../../assets/images/secondBg/star.png')
引入以后vue自己转化成了base64的编码,然后我们在需要使用的地方是“image://http://xxx.xxx.xxx/a/b.png”这种格式,我们自己需要加上'image://'这段字符,
icon: `image://${starImg}`
- 为了方便操作地图上的点,不和页面上其他部分重叠,我设置了地图鼠标缩放和平移漫游(geo坐标系中的roam),也就是鼠标在地图区域内的时候可以拖拽地图移动位置,也可以通过滑轮放大缩小地图面积,有个小瑕疵是有时候拖拽完之后鼠标已经没有左键右键控制了,但是鼠标一挪动,地图还是会跟着走,只有在地图之外的空白处点一下才会丢下拖拽。。。
- 本地图series中共有4个不同类型的数据组合,有的点经纬度离得近,地图缩小时会看上去重合,这时候就需要设置好每个类型的zlevel值,这是为了给每个类型绘制canvas时分层,不至于累在一起看不到别的点
最后附上我的全部代码
<template>
<div class="map-wrapper">
<v-chart :option="mapOption" ref="map" style="height: 100% !important;" auto-resize></v-chart>
</div>
</template>
<script>
import { allSchools, fetchSchoolDeatail } from '@/services/showView'
import echarts from 'echarts'
var config = require('../../../../static/mock/conf')
var dingnan = require(`../../../../static/mock/${config.map}`)
const starImg = require('../../../assets/images/secondBg/star.png')
echarts.registerMap('dingnan', dingnan)
const makeMapOption = (data) => {
return {
legend: {
show: true,
orient: 'vertical',
left: '22%',
top: '15%',
symbolKeepAspect: false,
itemWidth: 15,
itemHeight: 15,
data: [
{
name: '小学初中',
icon: 'circle',
textStyle: {
color: '#75eef5'
}
},
{
name: '高中中职',
icon: 'circle',
textStyle: {
color: '#f0ea75'
}
},
{
name: '完全中学',
icon: 'circle',
textStyle: {
color: '#887bff'
}
},
{
name: '县教育局',
icon: `image://${starImg}`,
textStyle: {
color: '#da4800'
}
}
]
},
tooltip: {
show: true,
triggerOn: 'click',
backgroundColor: 'rgba(10, 17, 64, 0.8)',
formatter(value, ticket, callback) {
let _this = this
fetchSchoolDeatail({ unitCode: value.data.schoolCode }).then(({ data, headers }) => {
let info = data.data
let str = `<div style="padding: 10px;">
<h4>${value.name}</h4>
<p>${info.bxTypeName}</p>
<p style="display: flex; line-height: 200%">
<span style="flex: 1 1 50%;margin-right: 30px;">在册学生<br/><b style="color: #62b2e2; font-size: 200%">${info.studentNum}</b>人 </span>
<span style="flex: 1 1 50%;">在册职工数<br/><b style="color: #65db8a; font-size: 200%">${info.teacherNum}</b>人</span>
</p>
<p style="display: flex; line-height: 200%">
<span style="flex: 1 1 50%;margin-right: 30px;">今日到校学生数<br/><b style="color: #62b2e2; font-size: 200%;">${info.studentArriveSchoolNum}</b> 人</span>
<span style="flex: 1 1 50%;">今日到校职工数<br/><b style="color: #65db8a; font-size: 200%">${info.teacherArriveSchoolNum}</b>人</span>
</p>
</div>`
callback(ticket, str)
}).catch(err => {
console.dir(err)
_this.$message.error(err.message || '获取学校信息失败,请稍后再试')
}).finally(() => {
console.log(1)
})
return '加载中...'
}
},
geo: {
map: 'dingnan',
label: {
emphasis: {
show: false
}
},
roam: true,
itemStyle: {
normal: {
color: 'rgba(43,58,113, 0.2)',
borderColor: '#0c6cd2'
},
emphasis: {
color: 'rgba(43,58,113, 0.2)'
}
}
},
series: [{
name: '县教育局', // 地图中心点
type: 'scatter',
coordinateSystem: 'geo',
zlevel: 10,
symbol: `image://${starImg}`,
data: [{
name: '县教育局',
value: data[3]
}],
symbolSize: [20, 20],
label: {
normal: {
formatter: '{b}',
position: 'top',
show: false
},
emphasis: {
show: false
}
},
itemStyle: {
normal: {
color: '#96edfe'
},
emphasis: {
show: false
}
}
},
{
name: '高中中职',
type: 'effectScatter', // 涟漪
coordinateSystem: 'geo',
zlevel: 2,
rippleEffect: {
brushType: 'stroke',
scale: 3
},
label: {
normal: {
fontSize: 16,
offset: [12, 0],
show: false,
position: 'right',
formatter: '{b}'
},
emphasis: {
show: false
}
},
symbolSize: 12,
itemStyle: {
normal: {
color: '#f0ea75'
}
},
data: data[1]
},
{
name: '小学初中',
type: 'effectScatter', //带有涟漪特效动画的散点
coordinateSystem: 'geo', // 该系统所使用的坐标系,geo地理坐标系
zlevel: 2, // zlevel用于 Canvas 分层
rippleEffect: {
brushType: 'stroke', // 波纹的绘制方式
scale: 3 // 动画中波纹的最大缩放比例
},
label: {
normal: {
fontSize: 16,
offset: [10, 0],
show: false,
position: 'right',
formatter: '{b}'
},
emphasis: {
show: false
}
},
symbolSize: 10, // 标记的大小
itemStyle: { // 图形样式
normal: {
color: '#75eef5'
}
},
data: data[0]
},
{
name: '完全中学',
type: 'effectScatter', // 涟漪
coordinateSystem: 'geo',
zlevel: 2,
rippleEffect: {
brushType: 'stroke',
scale: 3
},
label: {
normal: {
fontSize: 16,
offset: [10, 0],
show: false,
position: 'right',
formatter: '{b}'
},
emphasis: {
show: false
}
},
symbolSize: 8,
itemStyle: {
normal: {
color: '#887bff'
}
},
data: data[2]
}]
}
}
export default {
name: 'dingnan-map',
data() {
return {
mapname: config.mapname,
mapOption: {
series: [
]
},
mapChart: null,
coordData: {
name: '',
coord: [],
id: '',
children: [],
url3D: ''
},
geoCoordMap: {},
noticeList: [],
searchDeviceList: [],
allSecuityList: [],
isPlay: false,
playInt: '',
playCount: 1,
schoolName: ''
}
},
mounted() {
this.mapChart = this.$refs.map.chart
this.mapChart.showLoading({
text: '正在加载',
color: '#25D7FB',
textColor: '#25D7FB',
maskColor: 'rgba(19, 25, 83, 0.4)'
})
allSchools().then(({ data, headers }) => {
// 地图中的学校
if (data.resultCode === 0) {
let map = new Map()
for (let item of data.data) {
this.coordData.children.push({
schoolName: item.schoolName,
schoolId: item.schoolCode,
url3D: '',
coord: [item.longitude, item.latitude],
showColor: item.showColor
})
this.coordData.name = '定南县教体局'
this.coordData.coord = [115.049, 24.759872]
this.coordData.id = '3607280000'
this.coordData.url3D = ''
map.set(item.schoolName, [item.longitude, item.latitude])
}
this.geoCoordMap = map
this.mapOption = makeMapOption([this.convertEffectData(1), this.convertEffectData(2), this.convertEffectData(3), this.coordData.coord])
}
return { data: null }
}).catch(err => {
console.dir(err)
this.$message.error(err.message || '查询失败,请稍后再试')
}).finally(() => {
this.mapChart.hideLoading()
})
},
methods: {
// 涟漪点
convertEffectData(colorType) {
var res = [];
for (var i = 0; i < this.coordData.children.length; i++) {
var dataItem = this.coordData.children[i];
if (dataItem.showColor === colorType) {
res.push({
name: dataItem.schoolName,
value: dataItem.coord,
schoolCode: dataItem.schoolId
});
}
}
return res;
}
}
}
</script>
<style lang="scss" scoped>
.map-wrapper {
width: 98%;
height: 100%;
position: absolute;
left: 1%;
right: 1%;
margin: auto;
z-index: 1;
}
</style>
最后再啰嗦一下,我的地图小点点是一颗颗闪亮的小星星,这个主要设置好散点的颜色,涟漪点的圈圈数量,闪动的时间,就很好看啦,我的代码里都有注释。
第一次写这么多字,有点小鸡冻,分享就这么多了,如果您有幸看到了我的文章,希望能对您遇到的问题有所帮助,与君共勉,fighting!