随着大数据发展,可视化在前端的应用越来越广泛。近期利用高德地图做了一个关于数据可视化展示的项目,由于数据量大,使用点标记加载慢、重叠多且不美观,随后采用聚合标记对问题进行解决。
一、点标记
使用AMap.Marker构造点标记对象,最后添加到地图上。
/**绘制点标记
*@params points {Array} 待渲染的点标记数据
*/
handleMarker(points) {
for(let i = 0; i < points.length; i++) {
// 如果点标记的经纬度信息没有,则不进行渲染
if (!points[i].lng) {
continue
}
let marker = new AMap.Marker({
name: points[i].name, // 点标记名称
position: new AMap.LngLat(points[i].lng, points[i].lat), // 点标记经纬度
// 点标记样式
content: `<div style="background-color: #5366c3; height: 30px; width: 30px; border: 2px solid #ffffff; border-radius: 15px; box-shadow: 0px 0px 12px rgba(0,0,0,0.3);"></div>`,
// 位置偏置量,由于默认以左上角为基点,因此x和y方向都偏置一个半径的距离,将基点移至图形中心
offset: new AMap.Pixel(-15, -15),
})
// 添加点标注滑入事件
marker.on('mouseover', (e) => {
console.log(e.target.w.name)
.......
})
// 添加点标注滑出事件
marker.on('mouseout', (e) => {
.......
})
// 将所有点标记存入数组中,后续改造成聚合标记时需要用到
this.markers.push(marker)
}
}
二、聚合标记
当需要绘制的点数量级非常大时,上述的普通点标记渲染的方式就不适用了,在高德地图可通过AMap.MarkerClusterer这个插件对海量点标记进行聚合显示。
/**绘制点标记
*/
handleCluster() {
// 聚合标注滑入事件
let hanldeBubble = (e) => {
// 滑入聚合标记需要执行的事件
......
}
// 聚合标注滑出事件
let cancleBubble = (e) => {
// 滑出聚合标记需要执行的事件
......
}
AMap.plugin('AMap.MarkerClusterer', () => {
let count = this.markers.length;
// 设置聚合标记样式
let _renderClusterMarker = (context) => {
//移除上一次添加的滑入滑出事件,由于每次移动、放大缩小地图都会重新计算渲染聚合标注,因此必须移除之前的事件,否则事件会不断叠加导致重复
context.marker.off('mouseover', hanldeBubble)
context.marker.off('mouseout', cancleBubble)
//添加本次产生的新聚合标记的滑入滑出事件
context.marker.on('mouseover', hanldeBubble)
context.marker.on('mouseout', cancleBubble)
let extData = []
context.markers.forEach((item) => {
extData.push({
name: item.w.name
})
})
// 将标记名称等存入marker的拓展信息字段extData中
context.marker.extData = extData
let factor = Math.pow(context.count / count, 1 / 18);
let div = document.createElement('div');
let Hue = 180 - factor * 180;
let bgColor = this.bgColor
let fontColor = '#ffffff';
let borderColor = '#ffffff';
let shadowColor = 'rgba(0,0,0,0.3)';
div.style.backgroundColor = bgColor;
//标记的大小可以固定写,也可以根据聚合点的数量来决定大小
let size = 30
// let size = Math.round(30 + Math.pow(context.count / count, 1 / 5) * 20);
div.style.width = div.style.height = size + 'px';
div.style.border = 'solid 2px ' + borderColor;
div.style.borderRadius = size / 2 + 'px';
div.style.boxShadow = '0 0 12px ' + shadowColor;
div.innerHTML = context.count;
div.style.lineHeight = (size - 2) + 'px';
div.style.color = fontColor;
div.style.fontSize = '14px';
div.style.textAlign = 'center';
context.marker.setOffset(new AMap.Pixel(-size / 2, -size / 2));
context.marker.setContent(div)
}
this.cluster = new AMap.MarkerClusterer(
this.map, //指定地图对象
this.markers, //指定需要聚合的点标记对象
{
// averageCenter: false, // 聚合点的图标位置是否是所有聚合内点的中心点
gridSize: 100, //聚合计算时网格的像素大小,默认60
renderClusterMarker: _renderClusterMarker, //聚合点的自定义绘制
zoomOnClick: false, //点击聚合点时,是否散开
})
}
三、滑动标记弹出气泡
设计气泡的思路是:以固定定位写好气泡样式,当鼠标滑入点标记时,获取滑入标记的经纬度并转换为屏幕x和y方向上的像素位置,赋给气泡的left和top值,滑出标记时隐藏气泡即可。
1、气泡样式
html 代码
<div class="bubble-box" v-show="showBubble" :style="{left: bubleLeft + 'px', bottom: bubleTop + 'px'}">
<div class="bubble-wrap">
<div class="bubble-content" v-for="(item, index) in bubleList" :key="index">
<span></span>
<span>{{item.content}}</span>
</div>
</div>
</div>
css 代码
.bubble-box {
position: absolute;
margin-left: -6.375rem;
margin-bottom: 1.5625rem;
z-index: 990;
.bubble-wrap {
box-shadow: 0 0 .75rem rgba(0, 62, 255, 0.1);
position: relative;
width: 12.8125rem;
padding: .875rem;
font-size: 1rem;
color: $fontColor;
border-radius: .875rem;
background: #fff;
.bubble-content {
p {
line-height: 1.3;
& span:nth-child(1) {
margin-right: .375rem;
width: .5rem;
height: .5rem;
border-radius: .25rem;
display: inline-block;
background: $fontColor;
}
& span:nth-child(2) {
margin-right: .625rem;
}
}
}
&::after {
content: '';
display: inline-block;
width: .625rem;
height: .625rem;
background: #fff;
position: absolute;
bottom: -0.3125rem;
left: 50%;
margin-left: -0.3125rem;
transform: rotate(45deg);
border-radius: .125rem;
}
}
}
2、滑入事件--显示气泡
高德地图的聚合标记是基于点标记实现的,每一次进行地图平移、缩放操作时,AMap.MarkerClusterer插件都会调用renderClusterMarker里的方法对点标记根据gridSize设置的像素范围进行计算和渲染,点标记若在聚合范围内,则渲染成一个聚合点,不在范围内的点标记则不做处理(维持原样式和属性)。
因此需要对点标记和聚合标记分别添加滑入、滑出事件。
/* 点标记滑入事件*/
marker.on('mouseover', (e) => {
this.bubleList = []
//将点标记的name字段存入数组中,点标记的自定义数据都存放在e.target.w对象下
this.bubleList.push({
content: e.target.w.name
})
//获取点标记的经纬度
let lon = e.target.w.position.R
let lat = e.target.w.position.Q
//通过高德API中的lngLatToContainer将经纬度转换为屏幕对应x和y方向的坐标
let lnglat = new AMap.LngLat(lon, lat)
let pixel = this.map.lngLatToContainer(lnglat)
//将转换的坐标赋给气泡的left和top值,显示气泡
this.bubleLeft = pixel.x
this.bubleTop = document.body.clientHeight - pixel.y
this.showBubble = true
})
/*聚合标记滑入事件*/
let hanldeBubble = (e) => {
this.bubleList = []
//将点标记的name字段存入数组中,聚合标记的自定义数据都存放在e.target.extData对象下
e.target.extData.forEach((item) => {
this.bubleList.push({
content: item.name
})
})
//获取聚合后标记的经纬度
let lon = e.target.De.position.R
let lat = e.target.De.position.Q
//通过高德API中的lngLatToContainer将经纬度转换为屏幕对应x和y方向的坐标
let lnglat = new AMap.LngLat(lon, lat)
let pixel = this.map.lngLatToContainer(lnglat)
this.bubleLeft = pixel.x
this.bubleTop = document.body.clientHeight - pixel.y
this.showBubble = true
}
3、滑出事件--隐藏气泡
点标记和聚合标记滑出隐藏气泡的方法是一样的,不过由于二者绑定事件的方法不同,还是需要分开写。
/*隐藏气泡*/
hideBubble() {
this.showBubble = false
this.bubleList = []
}
/* 点标记滑出事件*/
marker.on('mouseout', (e) => {
this.hideBubble()
})
/*聚合标记滑出事件*/
let cancleBubble = (e) => {
this.hideBubble()
}
**4、聚合标记绑定滑入、滑出事件
高德地图的点标记本身支持滑入、滑出事件,直接使用marker.on(eventType, function())即可绑定相应的事件。而对于聚合标记,官方文档给出的支持事件只有click点击事件,因此聚合标记的其它事件绑定都需要特殊处理。
上面提到过,聚合标记是基于点标记的,每一次对地图进行缩放等操作时,都会重新调用renderClusterMarker方法计算和渲染聚合标记,而实际上这个聚合的标记是一个重新计算了经纬度和赋予特殊样式的点标记,因此可以在renderClusterMarker方法中为聚合标记绑定事件。
AMap.plugin('AMap.MarkerClusterer', () => {
// 设置聚合标记样式
let _renderClusterMarker = (context) => {
//移除上一次添加的滑入滑出事件,由于每次移动、放大缩小地图都会重新计算渲染聚合标注,因此必须移除之前的事件,否则事件会不断叠加导致重复
context.marker.off('mouseover', hanldeBubble)
context.marker.off('mouseout', cancleBubble)
//添加本次产生的新聚合标记的滑入滑出事件
context.marker.on('mouseover', hanldeBubble)
context.marker.on('mouseout', cancleBubble)
let extData = []
context.markers.forEach((item) => {
extData.push({
name: item.w.name
})
})
// 将标记名称等存入marker的拓展信息字段extData中
context.marker.extData = extData
......
})