vue + echarts + 百度地图 实现散点图框选
需求:根据坐标将所有零售柜用散点显示在地图上,散点大小代表柜子销售额大小。可以连续框选柜子,在地图右侧显示所选柜子销售额对比曲线。
完成效果图:
1.确定方案
这个需求的难点主要在于连续框选,且要获得框选的散点信息。一开始查资料发现 echarts 有个区域选择组件 brush(https://echarts.apache.org/zh/option.html#brush),可以实现连续框选。踩了很多坑勉强实现功能后发现还是有些问题,比如鼠标拖拽跟框选事件冲突,拖拽或放大地图选框会跟着移动不能保持在原位置……后来改变方案,利用百度地图 BMapLib 基础类的 DrawingManager 库实现框选。
2. 引入百度地图和工具库
index.html 中加入
<!-- 百度地图 --> <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=你的密钥"></script> <!-- library 基础类 --> <script type="text/javascript" src="https://api.map.baidu.com/library/GeoUtils/1.2/src/GeoUtils_min.js"></script> <!-- DrawingManager库--> <script type="text/javascript" src="https://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.js"></script>
webpack.base.conf.js 中加入(改完记得重启)
externals: { "BMapLib": "BMapLib" }
在组件中引入
import BMapLib from 'BMapLib'
3. echarts 配置
this.bmap = echarts.init(document.getElementById("map"));
配置分为几部分,首先是地图部分(配置的详细说明可以自行查找,我在官网上没有找到。地图与散点图配置可以参考官方实例 https://echarts.apache.org/examples/zh/editor.html?c=map-polygon):
bmap: { center: [104.114129, 37.550339], zoom: 5, roam: true, mapStyle: { styleJson: [ { featureType: "land", elementType: "geometry", stylers: { color: "#f5f6f7ff", }, }, { featureType: "water", elementType: "geometry", stylers: { color: "#c4d7f5ff", }, }, { featureType: "green", elementType: "geometry", stylers: { color: "#dcf2d5ff", }, }, { featureType: "highway", elementType: "all", stylers: { visibility: "off" }, }, ], }, }
散点图部分:
var data = [ {name: 'name1', value: 9}, {name: 'name2', value: 12}, {name: 'name3', value: 12} ] var geoCoordMap = { 'name1':[121.15,31.89], 'name2':[109.781327,39.608266], 'name3':[120.38,37.35] } var convertData = function (data) { var res = []; for (var i = 0; i < data.length; i++) { var geoCoord = geoCoordMap[data[i].name]; if (geoCoord) { res.push({ name: data[i].name, value: geoCoord.concat(data[i].value) }); } } return res; }; // 散点图配置 series: [ { type: "scatter", coordinateSystem: "bmap", data: this.convertData(this.data), symbolSize: function (val) { return val[2] / 10; } } ]
这里涉及到一个问题,散点图的大小怎么根据 value 设置,用上面的 symboSize 虽然能区别大小,但不好控制,如果 value 过大或过小显示在地图上都会很难看。最好的方法是给一个范围,让散点大小在这个范围里形成映射。数学不好就直接用工具了,D3js有个线性比例尺可以实现该功能(https://segmentfault.com/a/1190000011006780)
在 index.html 引入 D3js
<script src="https://d3js.org/d3.v5.js"></script>
symbolSize 改写如下
// 将所有 value 值放在数组 valueArr 里 var valueArr = []; data.forEach((item) => { valueArr.push(item.value); }); symbolSize: (val, params) => { // 将 valueArr 里的所有值映射到 [10, 40] 这个范围里 let scale = d3 .scaleLinear() .domain([ Math.min(...valueArr), Math.max(...valueArr), ]) .range([10, 40]); return scale(val[2]); },
散点是画好了,下一个问题又来了:给散点加 tooltip,拖拽地图的时候 tooltip 会跟着地图一起移动,不能停留在 hover 的点附近,解决办法参考:https://blog.csdn.net/wooden_people/article/details/89668999
增加 tooltip 设置:
tooltip: { trigger: "item", formatter: function (params, ticket, callback) { return ( params.value[4] + "<br />" + params.value[3] + "<br />" + params.name + "<br />" + "销售额: " + params.value[2] + "元" ); }, position: function (pos, params, dom, rect, size) { var top = $(".BMap_mask").css("top"); //.BMap_mask为固定格式 var left = $(".BMap_mask").css("left"); top = top.substring(0, top.length - 2); //去除px单位 left = left.substring(0, left.length - 2); return [pos[0] + 20 - left, pos[1] + 20 - top]; //20可根据需要调整 }, }
4. 框选功能实现
参考:http://lbsyun.baidu.com/jsdemo.htm#f0_7
值的注意的是,我们的 bmap 对象并不是通过 new BMap.Map('map') 创建的,而是以bmap属性来设置的,因此不能直接使用百度地图api,好在 echarts 提供了获取 bmap 对象实例的方法 ;
// 获取 bmap 对象实例 this.bmapModel = this.bmap.getModel().getComponent("bmap").getBMap(); // 设置边框样式 var styleOptions = { strokeColor: "red", //边线颜色。 fillColor: "red", //填充颜色。当参数为空时,圆形将没有填充效果。 strokeWeight: 1, //边线的宽度,以像素为单位。 strokeOpacity: 0.8, //边线透明度,取值范围0 - 1。 fillOpacity: 0.3, //填充的透明度,取值范围0 - 1。 strokeStyle: "solid", //边线的样式,solid或dashed。 }; var drawingManager = new BMapLib.DrawingManager(this.bmapModel, { isOpen: false, //是否开启绘制模式 enableDrawingTool: true, //是否显示工具栏 drawingToolOptions: { anchor: BMAP_ANCHOR_TOP_RIGHT, //位置 offset: new BMap.Size(5, 5), //偏离值 scale: 0.6, drawingModes: [BMAP_DRAWING_RECTANGLE], }, rectangleOptions: styleOptions, //矩形的样式 });
现在就可以框选地图上的点了!下一步是获取所框选的点的信息,如何知道哪些点被框住了呢?可以根据点的坐标来判断,已知每个点的坐标以及选框四个点的坐标,百度接口中有判断围栏的功能,即一个点是否在某个矩形中。参考:https://blog.csdn.net/u012539364/article/details/46648147
drawingManager.addEventListener("overlaycomplete", (e) => { var pStart = e.overlay.getPath()[3]; //矩形左上角坐标 var pEnd = e.overlay.getPath()[1]; //矩形右下角坐标 var pt1 = new BMap.Point(pStart.lng, pStart.lat); //3象限 var pt2 = new BMap.Point(pEnd.lng, pEnd.lat); //1象限 var bds = new BMap.Bounds(pt1, pt2); //范围 for (let key in geoCoordMap) { var pt = new BMap.Point( geoCoordMap[key][0], geoCoordMap[key][1] ); if (BMapLib.GeoUtils.isPointInRect(pt, bds)) { // 将框选的点存储在 deviceArr 中 if (this.deviceArr.indexOf(key) === -1) { this.deviceArr.push(key); } } } // 利用拿到的信息发起请求 ....... });
至此基本功能就完成了,至于肯定会存在的其他问题就不一一说明了,echarts 不同版本配置不同也挺让人头疼的,而且我还没有找到以前版本的文档。另外这个框选功能好像不支持移动端,留待后续探究。