zoukankan      html  css  js  c++  java
  • 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 不同版本配置不同也挺让人头疼的,而且我还没有找到以前版本的文档。另外这个框选功能好像不支持移动端,留待后续探究。

  • 相关阅读:
    小tips: HTTP 请求过程示例图及名称解释
    小tips:使用vuecli2脚手架配置vant自定义主题
    axios使用备忘录
    知乎问题:为什么很多web项目还是使用 px,而不是 rem?
    小tips:HTML5的ruby标签实现给汉字加拼音、details标签实现折叠面板、原生进度条progress和度量meter
    ES6之常用开发知识点:入门(一)
    GitBook相关使用以及配置笔记
    小tips:使用vue-cli脚手架搭建项目,关于eslint语法检测配置
    小tips:JS/CSS实现字符串单词首字母大写
    vue动态子组件的实现方式
  • 原文地址:https://www.cnblogs.com/zdd2017/p/13495908.html
Copyright © 2011-2022 走看看