🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
导言:优才网的春节相关的技术系列文章,今天继续关注百度迁徙,我们是从前端的角度来分析的。昨天谈完了数据和格式,今天来聊聊具体的 JS 实现。 昨天的文章里分析了百度迁徙的数据抓包和数据格式分析,了解了百度迁徙没有使用 Flash 和 SVG 技术,而是使用的 echarts 库,在Canvas上做的绘制,echarts 是百度前端团队的开源项目(PS,百度前端团队,有不少好的开源项目,如FIS、Ueditor、Tangram 都是百度前端开源的),据官方介绍,ECharts (Enterprise Charts 商业产品图表库),提供商业产品常用图表,底层基于ZRender(一个全新的轻量级canvas类库),创建了坐标系,图例,提示,工具箱等基础组件,并在此上构建出折线图、柱状图、散点图、K线图、饼图、雷达图、地图、和弦图、力导向布局图、仪表盘以及漏斗图,同时支持任意维度的堆积和多图表混合展现,也是目前所见的开源产品里图形支持最全面的。 不过迁徙毕竟是地图,所以在 echarts 上,有一层扩展,完成地图到图形操作和数据的映射,虽然 echarts 支持多种类型的图形,但是由于只用到了地图,所以在 Echarts的 chart 目录下只用到了 map.js。如果想了解或者学习绘制其他类型的图,可以进入 Github 下载源代码。https://github.com/ecomfe/echarts 下面就来相对细致地分析一下,是如何将数据取回来,并展现到地图上的。 我们从 mainIndex.js 入手来分析。正如昨天所说的机场和车站地理位置数据,在这个文件的第一行代码就是各个省的各个地级城市的地理位置数据。这也是一份很价值的数据哦。 var CityGeoCoord = [ {ProvinceName: "北京", LatLng: [116.395645, 39.929986]}, {ProvinceName: "上海", LatLng: [121.487899, 31.249162]}, {ProvinceName: "天津", LatLng: [117.210813, 39.14393]}, {ProvinceName: "重庆", LatLng: [106.530635, 29.544606]}, {ProvinceName: "安徽", LatLng: [117.216005, 31.859252], City: [{Name: "合肥", LatLng: [117.282699, 31.866942]}, {Name: "安庆", LatLng: [117.058739, 30.537898]}, {Name: "蚌埠", LatLng: [117.35708, 32.929499]}, {Name: "亳州", LatLng: [115.787928, 33.871211]}, {Name: "巢湖", LatLng: [117.88049, 31.608733]}, {Name: "池州", LatLng: [117.494477, 30.660019]}, {Name: "滁州", LatLng: [118.32457, 32.317351]}, {Name: "阜阳", LatLng: [115.820932, 32.901211]}, {Name: "淮北", LatLng: [116.791447, 33.960023]}, {Name: "淮南", LatLng: [117.018639, 32.642812]}, {Name: "黄山", LatLng: [118.29357, 29.734435]}, {Name: "六安", LatLng: [116.505253, 31.755558]}, {Name: "马鞍山", LatLng: [118.515882, 31.688528]}, {Name: "宿州", LatLng: [116.988692, 33.636772]}, {Name: "铜陵", LatLng: [117.819429, 30.94093]}, {Name: "芜湖", LatLng: [118.384108, 31.36602]}, {Name: "宣城", LatLng: [118.752096, 30.951642]}]}, 接下来,是一些日期处理的工具函数,由于在迁徙的页面上,使用了农历和阳历的对照,所以也有一个简单的农历计算工具函数。具体不详述。 然后是在地图上画标记线、标记点,去除线和点的函数。而对于绘制图的流程如下,以如下画线为例: mainIndex.js function AddMarkLine(a, c, d) { var b = $.extend(!0, {}, a.getOption().series[c].markLine); b && (b.data = d, a.addMarkLine(c, b)) } echarts.js(从Github库中得到) addMarkLine: function (seriesIdx, markData) { return this._addMark(seriesIdx, markData, 'markLine'); }, _addMark: function (seriesIdx, markData, markType) { var series = this._option.series; var seriesItem; if (series && (seriesItem = series[seriesIdx])) { var seriesR = this._optionRestore.series; var seriesRItem = seriesR[seriesIdx]; var markOpt = seriesItem[markType]; var markOptR = seriesRItem[markType]; markOpt = seriesItem[markType] = markOpt || { data: [] }; markOptR = seriesRItem[markType] = markOptR || { data: [] }; for (var key in markData) { if (key === 'data') { markOpt.data = markOpt.data.concat(markData.data); markOptR.data = markOptR.data.concat(markData.data); } else if (typeof markData[key] != 'object' || markOpt[key] == null) { markOpt[key] = markOptR[key] = markData[key]; } else { zrUtil.merge(markOpt[key], markData[key], true); zrUtil.merge(markOptR[key], markData[key], true); } } var chart = this.chart[seriesItem.type]; chart && chart.addMark(seriesIdx, markData, markType); } return this; }, 再接下来是DOM操作和事件绑定,还有其他的DOM工具函数。ChangeMenu 函数是一个切换菜单时调用的函数,在每一次菜单切换(也就是日期切换时)将各种数据重新初始化还原,包括界面上的输入清空,数据类型重置等。传入的参数是各种不同数据类型的类型编码。 function changeMenu(a) { MigrationResource.CurrentCondition.Province = ""; MigrationResource.CurrentCondition.City = ""; $("#input_cityName").attr("province", ""); $("#input_cityName").attr("city", ""); $("#input_cityName").val(""); $("#input_dateTime").val(""); MigrationResource.CurrentCondition.Flight = {}; $("#input_flight_start").val(""); $("#input_flight_end").val(""); MigrationResource.CurrentCondition.Type = Number(a); MigrationResource.CurrentTime = (new Date).pattern("yyyy/MM/dd HH:mm"); updateSelectedTime(); updateDataSource(); MigrationResource.FileName = ""; MigrationResource.ChinaJson = {}; MigrationResource.CityJson = {}; MigrationResource.CurrentTrendData = {}; MigrationResource.Query(RedrawMap) } 全局变量 MigrationResource 是一个非常重要的变量,里面包含了对地图的初始化、以及数据源的设定和数据获取。数据源的设定,是根据类型编号来的。比如11,12,13是航班数据。缺省的是人口迁徙数据,据主页君测试,有些数据比如代号为202的数据还没有。可能是将来要使用或者是废弃掉的。 switch (MigrationResource.CurrentCondition.Type) { case 11: case 12: case 13: b = "data/flight"; break; case 21: b = "data/huoche"; break; case 22: b = "data/huoche"; break; case 101: b = "data/plane"; break; case 102: b = "data/train"; break; case 103: b = "data/bus"; break; case 201: b = "data/scenicspot"; break; case 202: b = "data/business"; break; default: b = "data/migration" } 取数据的函数如下,是一个很清楚的使用 JSZipUtils 取数据的例子,并且是支持多文件的,取回来的数据是JSON格式,进行了解析,直接保存在MigrationResource数组中了。 function GetData(a, c, d, b, f, k) { JSZipUtils.getBinaryContent(DomainName + a + "/" + c, function(a, c) { a && "function" == typeof k && k(); try { var e = new JSZip; e.load(c); for (var p in e.files) for (var l = 0; l < b.length; l++) { var m = b[l]; if (0 <= p.indexOf(m.FileName)) { d[m.Attribute] = JSON.parse(e.file(p).asText()); break } } "function" == typeof f && f() } catch (n) { return!1 } }) } 接下来就是地图的处理了,地图先画出基础中国地图,数据来自于 http://spotshot.baidu.com/getChinaLocDistribute.php 在经过 Map 封装的 echarts 图上绘制出中国地图框架。 RedrawMap 是整个图形绘制过程中的核心函数,根据当前要显示的地图的不同,进行不同的数据绘制。在这个过程中,进行了一些限定 ,比如限制地图缩放等。我们也可以在Chrome 的开发者工具中,调用函数,EnableMapZoom() 来调试一下,让当前地图支持缩放。 function RedrawMap() { StopInterval(initMapKey); StopInterval(redrawMapKey); StopInterval(ipNumKey); $(".logo-box").show(); map.removeOverlay(heatmapOverlay); map.removeControl(top_left_navigation); isPc && ($("#timeline").show(), $(".btn_open_list").click()); $(".other-box").removeClass("other-box-1"); switch (MigrationResource.CurrentCondition.Type) { case -2: RemoveBaseMap(); DisableMapZoom(); RefreshMap(); isPc ? AddBaseLine(AddBasePoint, !0) : (AddBasePoint(), $("#mainMap").hide(), $("#mobileDiv").show().children("div").attr("class", "index-page-1")); break; case -1: AddHotLine(); break; case 0: AddHotPoint(0); break; case 1: AddHotPoint(1); break; case 2: RemoveBaseMap(); RefreshMap(); AddBasePoint(); AddCityHotLine(MigrationResource.CurrentCondition.Province, MigrationResource.CurrentCondition.City); break; case 3: RemoveBaseMap(); RefreshMap(); AddBasePoint(); AddCityHotLine(MigrationResource.CurrentCondition.Province, MigrationResource.CurrentCondition.City); break; case 11: RemoveBaseMap(); var a = phoneSizeZoom; isPc ? (a = flightSizeZoom, EnableMapZoom(a)) : DisableMapZoom(); AddFlight(); break; case 12: AddFlight(); break; case 13: AddFlight(); break; case 21: RemoveBaseMap(); DisableMapZoom(); AddTrain(); break; case 22: RemoveBaseMap(); DisableMapZoom(); AddTrain(); break; case 101: RemoveBaseMap(); DisableMapZoom(); AddAirport(); break; case 102: isPc ? (RemoveBaseMap(), DisableMapZoom(), AddRailwayStation()) : ($("#mainMap").hide(), $("#mobileDiv").show().children("div").attr("class", "index-page-2")); break; case 103: RemoveBaseMap(); DisableMapZoom(); AddBusStation(); break; case 201: RemoveBaseMap(); DisableMapZoom(); AddScenicspot(); break; case 202: RemoveBaseMap(), DisableMapZoom(), AddBusiness() } } 当然,要绘制出各种各样的图,得有相应的函数,比如机场是AddAirport() ,都通过 RedrawMap 来进行了调用。在人口迁徙中,迁入调用的是AddHotPoint(0) 迁出调用的是AddHotPoint(1); function AddHotPoint(a) { RemoveHotPoint(); RemoveHotLine(); var c = [], d = [], b = {}, b = 0 == a ? MigrationResource.ChinaJson.topCityIn : MigrationResource.ChinaJson.topCityOut; $(b).each(function(a, b) { var g = {}; g.name = GetCityName(b.name); g.CityLocation = b.name; g.value = a + 1; c.push(g); d.push(g) }); AddMarkPoint(myChart, 1, c); AddCityShowName(d) } 文件的后半部分,是有关右边的列表的实现。其中比较比意思的地方是实现了对拼音搜索的支持。 var provincialCapital = {"安徽": "合肥", "福建": "福州", "甘肃": "兰州", "广东": "广州", "广西": "南宁", "贵州": "贵阳", "海南": "海口", "河北": "石家庄", "河南": "郑州", "黑龙江": "哈尔滨", "湖北": "武汉", "湖南": "长沙", "吉林": "长春", "江苏": "南京", "江西": "南昌", "辽宁": "沈阳", "内蒙古": "呼和浩特", "宁夏": "银川", "青海": "西宁", "山东": "济南", "山西": "太原", "陕西": "西安", "四川": "成都", "新疆": "乌鲁木齐", "西藏": "拉萨", "云南": "昆明", "浙江": "杭州", "香港": "", "重庆": "", "北京": "", "天津": "", "上海": "", "台湾": "台北", "澳门": ""}, citys = ["香港 xianggang xg".split(" "), "香港 香港 xianggang xg xianggang xg".split(" "), "重庆 chongqing cq".split(" "), "重庆 重庆 chongqing cq chongqing cq".split(" "), "北京 beijing bj".split(" "), "北京 北京 beijing bj beijing bj".split(" "), "天津 tianjin tj".split(" "), "天津 天津 tianjin tj tianjin tj".split(" "), "上海 shanghai sh".split(" "), "上海 上海 shanghai sh shanghai sh".split(" "), "澳门 aomen am".split(" "), 这也是一份值得保存的数据和值得学习的做法。 地图绘制就基本讲完了,需要注意的一点,经纬度和屏幕像素等的转换,最终是通过百度地图类库来实现的。具体的办法是,在mainIndex 中调用 封装好的 echarts 库,而 echarts 库,又调用 百度地图API的库,百度地图 API 是通过这个 URL 取到的。 http://api.map.baidu.com/getscript?v=2.0&ak=ZUONbpqGBsYGXNIYHicvbAbM&services=&t=20150213143539 有关列表DOM操作不做过多介绍,我们下面来总结一下迁徙的实现。 百度迁徙,在2014年春节时推出,2015年春节,也被新闻媒体大量地引用,其在大数据产品化方面,有非常值得借鉴的意义,从技术上讲,无论是数据绘制、数据从服务器端的获取,还是本地的一些处理手法,都有值得我们学习的地方,优才网的公众账号,通过连续的两篇文章,对其做一个简单的分析,希望大家有用。 我们在春节期间推出的这些技术文章,得到了大家的欢迎。在后面,我们还将继续为大家奉上原创的技术文章 。 如果觉得我们的文章不错,欢迎关注优才网公众账号,以及查看原文,了解优才网的更多产品。