首先肯定是给出demo啦:
演示demo

直接到左侧选择框中选择View taxi flow里面随便选个日期

总体介绍

最近由于工作室项目需要做一个数据可视化平台,这个平台最终是交由国外人使用的。而国内的高德地图在国外是访问很慢,所以只能使用Google map进行实现(尝试过用AMap去实现,后面改回来)
回归主题:今天要讲的是迁徙图分别用两个平台进行实现

  • AMap + eCharts.js 实现迁徙图
  • Google map + D3.js 实现迁徙图

首先先来谈一下为什么都是在map上面进行数据可视化,但是实现迁徙图的技术不同(一个用eCharts、一个用d3)?!先来讲一下高德地图(AMap)和谷歌地图实现的技术区别吧(Google map)。

上面是高德地图,高德地图的实现是基于canvas进行实现的,显示效果都在这个画板上显示。接下来我们看一下谷歌地图是怎么实现的?

可以发现谷歌地图是由一张张图片拼接而成的,两者之间谁优谁劣不说,但是实现方式不同。正由于二者实现方式不同,所以飞线图实现方式也会有不同。

在github上面仅有开发echarts-amap中间插件(当然还有百度地图的echarts-bmap),并没有一个项目作为echarts和google map的中间件。这个项目给我的时间仅仅只有5天,所以不可能也没有能力去写这个插件。所以只能另外找资料实现google map + d3.js 飞线。


一、用echarts + amap 实现飞线,首先看一下效果图


这里有1000+条飞线数据,这是我随机找广州景区互相生成的假数据。因为既然今天讲到了两种技术实现飞线,那么顺带展示一下两个技术的性能区别,以1000条为例子。这个是怎么实现的呢?!我大概分成以下几步来进行分析:

  • 引入echarts-amap插件,如果是非vue框架的,去找一下echarts-amap插件(我没有用,所以无法提供)。如果是vue框架的话,npm i echarts-amap --save-dev进行安装,然后在项目中require(‘eharts-amap’)就行了。
  • 找到飞线所需的echarts配置
  • 初始化echarts,然后进行设置series,就和普通的echarts图标的流程一样,以下我给出例子。

下面是整个流程需要的代码,拿走就可以了。

let mychart = this.$echarts.init($('#' + id)[0]), // 初始化echarts到目标块中mychart.serOption({amap: {zoom: 10,zooms: [3, 20],mapStyle: 'amap://styles/darkblue', //地图主题center: [113.25, 23.1], //中心点lang: 'en',resizeEnable: true},animation: false,series: []});
// 以下对象的一个例子
FlyLineOption: {name: 'name',lineColor: '#fff',fromName: 'fromName',toName: 'toName',fromLngLat: [123, 23],toLngLat: [123, 23],value: 1,symbolColor: '#000'
}// 以下是echartsoption配置对象
{name: FlyLineOption.name,coordinateSystem: "amap",type: "lines",zlevel: 1,effect: {show: true,period: 6,trailLength: 0.7,color: "#fff",symbolSize: 3},lineStyle: {normal: {color: FlyLineOption.lineColor,width: 0,curveness: 0.2}},data: [{fromName: FlyLineOption.fromName,toName: FlyLineOption.toName,coords: [FlyLineOption.fromLngLat, FlyLineOption.toLngLat],value: FlyLineOption.value}]},{name: FlyLineOption.name,coordinateSystem: "amap",type: "lines",zlevel: 2,symbol: ["none", "arrow"],symbolSize: 10,lineStyle: {normal: {color: FlyLineOption.lineColor,width: 1,opacity: 0.6,curveness: 0.2}},data: [{fromName: FlyLineOption.fromName,toName: FlyLineOption.toName,coords: [FlyLineOption.fromLngLat, FlyLineOption.toLngLat],value: FlyLineOption.value}]},{name: FlyLineOption.name,type: "effectScatter",coordinateSystem: "amap",zlevel: 2,rippleEffect: {brushType: "stroke"},label: {normal: {show: true,position: "bottom",formatter: "{b}"}},itemStyle: {normal: {color: FlyLineOption.symbolColor}},data: [{name: FlyLineOption.toName,value: [...FlyLineOption.toLngLat, FlyLineOption.value]}]}

这样即可绘制飞线图。echarts这个插件其实蛮好用的,很多图都有,也比较齐全。它里面的飞线是二次贝塞尔曲线,会自动帮你绘制这条贝塞尔曲线。
但是换成google map并不会像国内的地图有这么好的生态了。可能国外会有一些插件去支持它,但是目前来看我是找不到。那么我是怎么去实现的呢?

二、Google map + D3.js 实现飞线

先讲一下总体的思路:

  1. 添加谷歌地图图层,进行绘制svg标签
  2. 利用d3在谷歌地图图层进行绘制svg图
  3. 首先将gps化为谷歌坐标,求出两点之间的贝塞尔曲线(需要自己寻找控制点),并且绘制出来(利用path标签自带的贝塞尔曲线绘制方法)

Google map 初始化

首先,你要有一个Google map 的ID,这个百度查询如何使用,然后最好是引用国内镜像,毕竟VPN来直接访问还是有点慢。

<script src="http://ditu.google.cn/maps/api/js?&key=&language=en"></script>

接来下进行地图的初始化,同样上代码

let map = new google.maps.Map(document.getElementById(this.id), {zoom: 8,center: {lat: 23.1,lng: 113.25},disableDefaultUI: true,               //取消默认的试图navigationControl: false,              //true表示显示控件mapTypeControl: false,                //false表示不显示控件scaleControl: false});

Google map 渲染技术简单介绍

初始化完毕后,我先给大家简单介绍一下google map api提供地图的技术吧。

  1. google map如何渲染?
    google map分成20左右的zoom层数,即放大层数,每个层数都有固定的地图的图片,将这些地图的图片进行拼装,就会出现整个世界地图的模型。

    还是老样子直接上图,在进行渲染、拖拽、缩放的时候,系统会识别当前显示的区域,进行请求显示区域所需要的图片,请求完毕后进行渲染。
  2. 如何在google map 上添加自定义的图层呢?
    在以canvas为基础的地图中,我们可以用两个canvas重叠后来进行渲染,即地图canvas在下面,echarts的canvas在上面,从而实现飞线效果。
    但是google map并不行,它不是这种技术实现,但是它也可以进行添加自定义图层,创建一个对象,new google.maps.OverlayView();这个对象返回一个创建的图层。它有add、draw两个属性比较重要,在本项目中会用到。
    特别地,在google map上绘画东西是需要坐标的,即横坐标和纵坐标进行绘制。这个正是google map渲染时候的技术决定的(很多图片组成,图片间是紧挨着的,这个是通过创建元素构成的。也就是需要有一个特别大的容器装下这些图片。绘制的时候只能用坐标来进行绘制)。

Google map 图层的建立

那么下面就来给大家手把手教大家如何建立图层。google map官网有一个例子进行比较正规方法的,它是通过新建一个OverlayView对象实例,对实例对象的原型进行修改add、draw方法,然后以这个实例对象为原型,进行原型链继承。那个方法太麻烦了,我这边就直接来个简单的。

let overlay = new google.maps.OverlayView();
overlay.onAdd = () => {// 这个函数式图层加载完毕后就执行一遍时候的事件监听函数(可以这么理解)
}
overlay.draw = () => {// 这个是地图每一次绘画完毕后进行的事件监听执行函数(绘画地图包括左右移动和缩放)
}

现在大家已经会进行图层的建立了,那么如何在图层上进行绘制呢?首先要得到绘制的坐标。google map有一个方法将经纬度转为地图做标。

let project = overlay.getProjection();
let latLng = new google.maps.LatLng(23, 123)
let axios = projection.fromLatLngToDivPixel(latLng);
axios.x, axios.y //这两个属性就是x值和y值

上面得到的坐标是全局绝对坐标,可能会很大。但是有显示的图层只有在屏幕范围内,所以要进行坐标的转换。如何理解呢,我画了以下的图。

我们需要将目标点坐标减去视图层的左上角的坐标,就能得到相对坐标了。

前面的代码已经将想要描述的点通过经纬度转为坐标了,那么现在就要进行绘制了,首先要获取overlay定义的图层,这个图层在overlay添加完毕后可以使用,代码如下:

overlay.onAdd = () => {let layer = overlay.getPanes().overlayLayer;  // 获取绘制容器// 然后通过appendChild进行绘制,使用绝对定位,设置left和right进行绘制,这样就可以了。// 这个是涉及到d3的一些操作,可以去了解以下d3的操作。// 下面是我的一些代码,可以拿去当做参考let layer = d3.select(overlay.getPanes().overlayLayer).append('svg').attr('class', 'fly-layer');  // 创建svg标签let defs = layer.append('defs');   // 添加defs属性列表defs.append('marker')                // 添加marker标签(svg标签的)这个是添加箭头的样式,因为svg标签只需要一个,所以在创建svg标签的时候就创建了.attr('id', 'markerArrow').attr('markerWidth', '13').attr('markerHeight', '13').attr('refX', '2').attr('refY', '6').attr('orient', 'auto').append('path').attr('d', 'M2,2 L2,11 L10,6 L2,2').attr('fill', '#FF0000');// 下面是对data进行批量添加数据,此时并没有绘制path路径,而是添加flyLine类名而已,这个是方便后面进行获取容器layer.selectAll('.flyLine').data(data, (d) => d.id).enter().append('path').attr('class', 'flyLines')let projection = overlay.getProjection();
}

d3可以去它的官网查看文档怎么使用
https://d3js.org/
但是,你想看懂d3的操作,首先要知道svg矢量图是什么,怎么用的。我曾经做过一个用svg矢量图实现的流动效果的网页作为我们团队的招新网:
http://47.102.136.151:4000/dist/index.html
如果弄懂了里面svg图的变化,基本上是可以上手任何svg矢量图了。
把话题拉回来了,上面是获取绘制图层块并且在里面进行添加svg标签,并且添加一个通用的箭头效果。那么接下来来谈谈这个overlay图层怎么添加draw方法。

// draw方法是地图在缩放或者鼠标拖动的时候会触发的事件,所以在短时间会触发很多次,这个要注意到
overlay.draw = () => {let bounds = this.map.getBounds();let ne = bounds.getNorthEast(),  // 获取当前显示区域右上角的对象(可通过访问获取右上角坐标)sw = bounds.getSouthWest(),   // 获取当前显示区域左下角的对象center = bounds.getCenter();      // 获取中心点坐标layer.selectAll('.flyLines').data(data)   // 进行更新数据.each(transform)  // 对每个数据渲染的对象进行修改,将transform的返回值作为这个each的参数// 这里为了方便我把下面两个函数就直接贴在这里// 这个函数计算出两个坐标点贝塞尔曲线控制点相对于两个坐标点中点的位置,isContract是为了让绘制结果更好看function getCosFromTan(d1, d2, isContract) {let value, rad;if (d2.x == d1.x) {value = 0;rad = Math.PI / 2;} else {value = (d2.y - d1.y) / (d2.x - d1.x),rad = Math.atan(Math.abs(value));}let length = Math.pow(Math.pow(d2.y - d1.y, 2) + Math.pow(d2.x - d1.x, 2), 1 / 2),paramX, paramY;if (value < 0) {paramX = 1;paramY = 1;} else {paramX = 1;paramY = -1;}return isContract ? {x: paramX * length * Math.sin(rad),y: paramY * length * Math.cos(rad)} : {x: -(paramX * length * Math.sin(rad)),y: -(paramY * length * Math.cos(rad))}}function transform(flyObj) {// 将原生的gps坐标改为谷歌地图坐标let fromLngLat = new google.maps.LatLng(flyObj.fromLnglat[1], flyObj.fromLnglat[0]);let toLngLat = new google.maps.LatLng(flyObj.toLnglat[1], flyObj.toLnglat[0]);// 将右上角的坐标转为谷歌地图坐标let neLngLat = new google.maps.LatLng(ne.lat(), ne.lng())// 将左下角的坐标转为谷歌地图坐标let swLngLat = new google.maps.LatLng(sw.lat(), sw.lng())// 将gps坐标转为平面直角坐标系的坐标fromLngLat = projection.fromLatLngToDivPixel(fromLngLat );toLngLat = projection.fromLatLngToDivPixel(toLngLat);let bashX = projection.fromLatLngToDivPixel(neLngLat).x;  // 获取可视区域左边的坐标let bashY = projection.fromLatLngToDivPixel(swLngLat).y // 获取可是区域上边的坐标let res = getCosFromTan(fromLngLat, toLngLat , isContract);  // 获取二次贝塞尔曲线的控制点的相对于两点的坐标if (isContract == true) {isContract = false;} else {isContract = true;}randomBash =  (randomBash + 1) % 10;// 获取贝塞尔曲线的控制点坐标res.x = bashX + res.x * (randomBash / 10) + (toLngLat .x + fromLngLat.x) / 2;  res.y = bashY + res.y * (randomBash / 10) + (toLngLat .y + fromLngLat.y) / 2;let color = that.getValeColor(flyObj.value, rankUnit)return d3.select(this)// 绘制贝塞尔曲线.attr('d', 'M ' + (bashX + fromLngLat.x) + ',' + (bashY + fromLngLat.y) + ' Q ' + res.x + ',' + res.y + ' ' + (bashX + toLngLat .x) + ',' + (bashY + toLngLat .y)).attr('stroke', color).attr('stroke-width', '1').attr('fill', 'none').attr('marker-end', 'url(#markerArrow)');}
}

这里留下一个分割线来解释一下那个求控制点方法的函数。。。


  • 首先,先创建当前谷歌地图的layer,这个新建的对象回去监听地图的重绘和图层的添加完毕事件,但是这个事件的回调函数需要我们去定义。
  • 当layer创建完毕的时候,进行初始化svg标签,以后的图都要在这个svg标签上面绘制东西,最好是给这个svg添加一个类,然后将svg调整为宽度是100vw,100vh大小,并且要讲这个svg图完全显示到可视区域上(使用以下css样式)。
// 这个图层是固定在可视区域的(也正是这样,需要在draw方法中每次重新绘制飞线)。并且是从页面的中点开始绘制的,所以需要将其调整一下。
{width: 100vw;height: 100vh;position: absolute;top: -50vh;left: -50%;
}
  • 在draw回调函数中利用数据添加飞线
  • 为什么top和left要进行设置-50%?这是因为svg的画板的横纵坐标是从可视区域的中点开始画起的。我们要的是可视区域都是svg的画板,所以需要尽心设置偏移。
    明天再回来补充、先睡了

补充,之前还没有讲完,现在进行补充完毕


利用数据进行添加飞线。需要用到d3数据可视化库的一个selectAll选择已经渲染后的节点,并且进行遍历数据进行渲染。我们在draw代码中提取对数据进行更新的代码。

layer.selectAll('.flyLines').data(data)   // 进行遍历数据(数据要以一个数组的形式).each(transform)  // 这个是数据的遍历,这里transform是对每个数据的回调

上面的each的用法类似于JavaScript中对于数组的遍历的用法(针对于被遍历的数组每个数据进行设置针对于google map的操作)。
我先说transfrom的作用:不加这个方法的话,所有的线都是执行,所以没有那种弯曲的感觉,transform的作用就是将线条变成飞线。

接下来我们顺藤摸瓜找到了transform方法,如下代码。

function transform(flyObj) {// 将原生的gps坐标改为谷歌地图坐标let fromLngLat = new google.maps.LatLng(flyObj.fromLnglat[1], flyObj.fromLnglat[0]);let toLngLat = new google.maps.LatLng(flyObj.toLnglat[1], flyObj.toLnglat[0]);// 将右上角的坐标转为谷歌地图坐标let neLngLat = new google.maps.LatLng(ne.lat(), ne.lng())// 将左下角的坐标转为谷歌地图坐标let swLngLat = new google.maps.LatLng(sw.lat(), sw.lng())// 将gps坐标转为平面直角坐标系的坐标fromLngLat = projection.fromLatLngToDivPixel(fromLngLat );toLngLat = projection.fromLatLngToDivPixel(toLngLat);let bashX = projection.fromLatLngToDivPixel(neLngLat).x;  // 获取可视区域左边的坐标let bashY = projection.fromLatLngToDivPixel(swLngLat).y  // 获取可是区域上边的坐标let res = getCosFromTan(fromLngLat, toLngLat , isContract);  // 获取二次贝塞尔曲线的控制点的相对于两点的坐标if (isContract == true) {isContract = false;} else {isContract = true;}randomBash =  (randomBash + 1) % 10;// 获取贝塞尔曲线的控制点坐标res.x = bashX + res.x * (randomBash / 10) + (toLngLat .x + fromLngLat.x) / 2;  res.y = bashY + res.y * (randomBash / 10) + (toLngLat .y + fromLngLat.y) / 2;let color = that.getValeColor(flyObj.value, rankUnit)return d3.select(this)// 绘制贝塞尔曲线.attr('d', 'M ' + (bashX + fromLngLat.x) + ',' + (bashY + fromLngLat.y) + ' Q ' + res.x + ',' + res.y + ' ' + (bashX + toLngLat .x) + ',' + (bashY + toLngLat .y)).attr('stroke', color).attr('stroke-width', '1').attr('fill', 'none').attr('marker-end', 'url(#markerArrow)');}

首先要明确我们要干什么?

  • 得到可是区域左上角在谷歌的DivPixel坐标作为基础的坐标(相对于谷歌的平面坐标系的坐标)
  • 得到出发点的坐标以及终止点的坐标(仅仅是相对于svg画板的左上角的坐标)
  • 根据出发点和终点求出贝塞尔曲线控制点的坐标(仅仅是相对于svg画板左上角的坐标)
  • 将控制点、起点、终点都加上基础坐标

这样就可以进行确定我们所要显示的坐标在画布上的位置(包括起点、终点、控制点),接下来就可以通过svg矢量图来进行绘制贝塞尔曲线了。接下来我就直接晒出用d3来画的成果吧。我这里就不给出项目地址了。

这个是显示所有的飞线的效果图。大概就几万条,如果用canvas来进行绘制的话,可能会因为现实内容过多将浏览器卡死。
而用svg矢量图的话,它是作为dom节点会知道html上面的,会比canvas性能好一点。但也不会好到哪里去(因为放大或者缩小、移动地图的时候,svg里面的标签需要重新渲染(因为画布永永远是跟随着用户的,并且只有屏幕大小,只会渲染屏幕现实的内容,所以移动一次交渲染一次))。
如果是dom标签的渲染的话,就回到了我们的舒适圈了。进行优化的话我这里就不列出代码了,提供几个思路。

  1. 减少渲染的次数,比如说按住地图进行拖动的时候,不是一移动屏幕就重新渲染,而是等到用户停止移动的时候才会进行渲染。(默认是只要用户一移动屏幕,就会重新渲染)
  2. 如果听说过线程池的概念的话,就很好理解了。渲染dom节点花费的性能很贵的,那么我们就造一个dom节点池来进行优化。
    最后的最后,我还是人性化一点,贴几张能看的google map + d3的飞线效果图吧。

最后如果想要我的源码的话,请到

Github: MapDemo

如果觉得可以的话,请点个赞并且Star一下谢谢!

AMap + echarts、google map + d3.js分别实现数据可视化中的飞线图(迁徙图)相关推荐

  1. 基于echarts+js+fexible.js实现的数据可视化适配案例(附源代码)

    介绍 数据可视化 :借助于图形化手段,清晰有效地传达与沟通信息 ECharts:是一个使用JavaScript实现的开源可视化库,可以流畅的运行在PC和移动设备上,兼容当前绝大部分浏览器,底层依赖矢量 ...

  2. 利用d3.js对大数据资料进行可视化分析

    insight-labs · 2013/12/19 18:18 作者: [email protected] [email protected] 0x00 背景 对于前段时间流出的QQ群数据大家想必已经 ...

  3. Vue项目中Echarts流向图迁徙图实现

    在数据可视化中,地图可视化是高频应用的一种.我们在一些新闻报道和商业杂志上,会经常看到运用地图来分析展示商业现象,这样的利用地图来反映和分析数据的形式叫数据地图.数据地图可以最直观的表达出数据之间的空 ...

  4. 用 node.js 来制作数据可视化视频吧

    TNTWeb - 全称腾讯新闻前端团队,组内小伙伴在 Web 前端.NodeJS 开发.UI 设计.移动 APP 等大前端领域都有所实践和积累. 目前团队主要支持腾讯新闻各业务的前端开发,业务开发之余 ...

  5. 结合d3.js实现气象数据的可视化

    概述 本文将结合d3.js实现在mapboxGL中格点气象数据的展示. 效果 实现 1.数据格式说明 需要将格点气象数据实现前端的展示,数据传输的方式有三种:1.json:2.二进制:3.灰度图.三种 ...

  6. 基echarts实现的地图,大屏数据可视化

    基echarts实现的地图,大数据数据可视化 1.课程目标: 实践css布局相关技术 实践jquery相关技术 掌握echarts的基本使用 2.项目介绍 我们要完成这个项目:需要使用一些基础的DIV ...

  7. echarts 飞线、轨迹图与地方地图联动

    首先展示一下需要实现的效果,利用两个坐标点数据,实现飞线,与地图的坐标进行联动. 在实现这块功能时,两个坐标体系的联动是难点. 其中地图的series type是:map, 另一个飞线的 series ...

  8. 暑期项目实训:基于webgl(three.js)的牙科数据可视化展示

    第一天: 本组的选题是"牙科数据的分割与分类及可视化展示平台".工作主要划分成三块:前端平台页面搭建.算法与数据可视化.后端及数据库.我和另一个同学一起负责算法与数据可视化. 根据 ...

  9. h5 数字变化_前端/h5 D3.js实现根据数据动态更新图形/类似进度实时变化效果

    最近接到一个需求,在满足规则下,实现类似这种展示效果,其实就是用图形反映数据(NK,一种干扰值) 运行后,它其实是不断在动的,每格都可能显示灰色或者彩色 这里一共是10个格子,每格代表一个范围边界,说 ...

最新文章

  1. “波士顿动力机器人”离上战场又近了一步,逆天了!
  2. 3D相机的数据处理方式
  3. Linux 下 svn 的使用
  4. 别怀疑,换了位置就该换你的脑袋(转)
  5. 深度学习核心技术精讲100篇(四十九)-半监督学习在金融文本分类上的探索和实践
  6. 有没有词匹配算法_整站关键词SEO的匹配优化方法
  7. NLog 2.0.0.2000 使用实例
  8. oracle 开并行写入命令,Oracle等待事件“日志文件并行写入”更改
  9. LEADTOOLS构建HTML5 DICOM/PACS查看器
  10. mysql的底层运行原理,【数据库】震惊!!MySQL的底层原理竟然是这样
  11. C语言代码绘制,利用数组输出 0-2Π之间的 sin 函数图像和 cos 函数图像,实验报告及代码。
  12. hadoop工作原理
  13. 天语W700 wipe
  14. 简述oracle_sid,环境变量 ORACLE_SID 简述
  15. 利用python制作拼图_用python做一个三阶拼图
  16. 一口气搞懂「Flink Metrics」监控指标和性能优化,全靠这33张图和7千字(建议收藏)
  17. 视频文件服务器加速,视频CDN,视频点播加速,视频点播CDN加速,CDN流媒体点播加速_速网云计算CDN服务商·卓越的互联网业务平台提供商...
  18. Python 零基础入门到实战(一)笔记:内置对象、浅拷贝、深拷贝、计算圆面积、凯撒密码、英文大小写转换、输入国家打印出国家名和首都、输入数字英文输出、统计句子中的字母数量、猜随机数
  19. 每天一个效果 :(14)抽奖器功能
  20. 【java基础知识】——jdk和jre的区别

热门文章

  1. SpringBoot系列之Hikari连接池
  2. 计算机兴趣画图活动记录,电脑绘画兴趣小组活动记录.doc
  3. Cocos Creator:解密华容道-数字拼盘
  4. 数据挖掘Java——DBSCAN算法的实现
  5. 2022 虎年除夕,苏生不惑送现金红包来了
  6. nginx 端口禁止浏览器访问
  7. Go语言学习之cgo(golang与C语言相互调用)
  8. 华为nova5pro能换鸿蒙系统吗,华为nova7se可以升级鸿蒙系统吗
  9. WordPress安装插件提示:发生了预料之外的错误。
  10. Apache Kafka教程