需求:根據坐標將所有零售櫃用散點顯示在地圖上,散點大小代表櫃子銷售額大小。可以連續框選櫃子,在地圖右側顯示所選櫃子銷售額對比曲線。
完成效果圖:
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 不同版本配置不同也挺讓人頭疼的,而且我還沒有找到以前版本的文檔。另外這個框選功能好像不支持移動端,留待后續探究。