echarts3结合openlayers2实现Map类型图表
网上查阅了部分资料,有些是用echarts2实现的,因echarts2无法满足项目中其他部分的要求,故只能采用echarts3(2017/9/18,echarts3官网突然把基于geo的demo下架了,只剩下基于百度地图的示例,汗-_-||)。
参考:Echart2在Openlayers2的应用-航班的炫光特效
openlayers3与echarts3的结合使用(普通图表-pie、bar……)
Leaflet 地图Leaflet 地图和 ECharts 的结合,参考前端代码
扩展OpenLayers.Layer图层,用于装载Map类型图表,侦听地图刷新事件,同步进行echarts的Option中坐标变换,转变为屏幕坐标;
1 <!DOCTYPE html> 2 <head> 3 <meta charset="utf-8"> 4 <title>ECharts</title> 5 <script type="text/javascript" src="resources/js/jquery-3.1.0.min.js"></script> 6 <script type="text/javascript" src="resources/js/ol2-release-2.13.1/OpenLayers.js"></script> 7 <link rel="stylesheet" type="text/css" href="resources/js/ol2-release-2.13.1/theme/default/style.css"> 8 <!-- ECharts单文件引入 --> 9 <script type="text/javascript" src="resources/js/echarts.js"></script> 10 <script type="text/javascript" src="resources/js/EchartMapLayer.js"></script> 11 12 <style> 13 #map{ 14 width:1000px; 15 height: 700px; 16 position: absolute; 17 } 18 </style> 19 </head> 20 <body> 21 <div id="map" class="smallmap"></div> 22 <script type="text/javascript"> 23 var map = new OpenLayers.Map('map', { 24 controls: [ 25 new OpenLayers.Control.Navigation(), 26 new OpenLayers.Control.PanZoomBar(), 27 new OpenLayers.Control.LayerSwitcher({'ascending':false}), 28 new OpenLayers.Control.ScaleLine(), 29 new OpenLayers.Control.MousePosition(), 30 new OpenLayers.Control.OverviewMap(), 31 // new OpenLayers.Control.KeyboardDefaults() 32 ], 33 numZoomLevels: 17 34 }); 35 36 var osmlayer = new OpenLayers.Layer.OSM("OSM"); 37 map.addLayers([osmlayer]); 38 39 map.setCenter(new OpenLayers.LonLat(11400246.56,4439064.42), 4); 40 41 //create echarts's map 42 43 var geoCoordMap = { 44 '上海': [121.4648, 31.2891], 45 '东莞': [113.8953, 22.901], 46 '东营': [118.7073, 37.5513], 47 '中山': [113.4229, 22.478], 48 '临汾': [111.4783, 36.1615], 49 '临沂': [118.3118, 35.2936], 50 '丹东': [124.541, 40.4242], 51 '丽水': [119.5642, 28.1854], 52 '乌鲁木齐': [87.9236, 43.5883], 53 '佛山': [112.8955, 23.1097], 54 '保定': [115.0488, 39.0948], 55 '兰州': [103.5901, 36.3043], 56 '包头': [110.3467, 41.4899], 57 '北京': [116.4551, 40.2539], 58 '北海': [109.314, 21.6211], 59 '南京': [118.8062, 31.9208], 60 '南宁': [108.479, 23.1152], 61 '南昌': [116.0046, 28.6633], 62 '南通': [121.1023, 32.1625], 63 '厦门': [118.1689, 24.6478], 64 '台州': [121.1353, 28.6688], 65 '合肥': [117.29, 32.0581], 66 '呼和浩特': [111.4124, 40.4901], 67 '咸阳': [108.4131, 34.8706], 68 '哈尔滨': [127.9688, 45.368], 69 '唐山': [118.4766, 39.6826], 70 '嘉兴': [120.9155, 30.6354], 71 '大同': [113.7854, 39.8035], 72 '大连': [122.2229, 39.4409], 73 '天津': [117.4219, 39.4189], 74 '太原': [112.3352, 37.9413], 75 '威海': [121.9482, 37.1393], 76 '宁波': [121.5967, 29.6466], 77 '宝鸡': [107.1826, 34.3433], 78 '宿迁': [118.5535, 33.7775], 79 '常州': [119.4543, 31.5582], 80 '广州': [113.5107, 23.2196], 81 '廊坊': [116.521, 39.0509], 82 '延安': [109.1052, 36.4252], 83 '张家口': [115.1477, 40.8527], 84 '徐州': [117.5208, 34.3268], 85 '德州': [116.6858, 37.2107], 86 '惠州': [114.6204, 23.1647], 87 '成都': [103.9526, 30.7617], 88 '扬州': [119.4653, 32.8162], 89 '承德': [117.5757, 41.4075], 90 '拉萨': [91.1865, 30.1465], 91 '无锡': [120.3442, 31.5527], 92 '日照': [119.2786, 35.5023], 93 '昆明': [102.9199, 25.4663], 94 '杭州': [119.5313, 29.8773], 95 '枣庄': [117.323, 34.8926], 96 '柳州': [109.3799, 24.9774], 97 '株洲': [113.5327, 27.0319], 98 '武汉': [114.3896, 30.6628], 99 '汕头': [117.1692, 23.3405], 100 '江门': [112.6318, 22.1484], 101 '沈阳': [123.1238, 42.1216], 102 '沧州': [116.8286, 38.2104], 103 '河源': [114.917, 23.9722], 104 '泉州': [118.3228, 25.1147], 105 '泰安': [117.0264, 36.0516], 106 '泰州': [120.0586, 32.5525], 107 '济南': [117.1582, 36.8701], 108 '济宁': [116.8286, 35.3375], 109 '海口': [110.3893, 19.8516], 110 '淄博': [118.0371, 36.6064], 111 '淮安': [118.927, 33.4039], 112 '深圳': [114.5435, 22.5439], 113 '清远': [112.9175, 24.3292], 114 '温州': [120.498, 27.8119], 115 '渭南': [109.7864, 35.0299], 116 '湖州': [119.8608, 30.7782], 117 '湘潭': [112.5439, 27.7075], 118 '滨州': [117.8174, 37.4963], 119 '潍坊': [119.0918, 36.524], 120 '烟台': [120.7397, 37.5128], 121 '玉溪': [101.9312, 23.8898], 122 '珠海': [113.7305, 22.1155], 123 '盐城': [120.2234, 33.5577], 124 '盘锦': [121.9482, 41.0449], 125 '石家庄': [114.4995, 38.1006], 126 '福州': [119.4543, 25.9222], 127 '秦皇岛': [119.2126, 40.0232], 128 '绍兴': [120.564, 29.7565], 129 '聊城': [115.9167, 36.4032], 130 '肇庆': [112.1265, 23.5822], 131 '舟山': [122.2559, 30.2234], 132 '苏州': [120.6519, 31.3989], 133 '莱芜': [117.6526, 36.2714], 134 '菏泽': [115.6201, 35.2057], 135 '营口': [122.4316, 40.4297], 136 '葫芦岛': [120.1575, 40.578], 137 '衡水': [115.8838, 37.7161], 138 '衢州': [118.6853, 28.8666], 139 '西宁': [101.4038, 36.8207], 140 '西安': [109.1162, 34.2004], 141 '贵阳': [106.6992, 26.7682], 142 '连云港': [119.1248, 34.552], 143 '邢台': [114.8071, 37.2821], 144 '邯郸': [114.4775, 36.535], 145 '郑州': [113.4668, 34.6234], 146 '鄂尔多斯': [108.9734, 39.2487], 147 '重庆': [107.7539, 30.1904], 148 '金华': [120.0037, 29.1028], 149 '铜川': [109.0393, 35.1947], 150 '银川': [106.3586, 38.1775], 151 '镇江': [119.4763, 31.9702], 152 '长春': [125.8154, 44.2584], 153 '长沙': [113.0823, 28.2568], 154 '长治': [112.8625, 36.4746], 155 '阳泉': [113.4778, 38.0951], 156 '青岛': [120.4651, 36.3373], 157 '韶关': [113.7964, 24.7028] 158 }; 159 160 var BJData = [ 161 [{name: '北京'}, {name: '上海', value: 95}], 162 [{name: '北京'}, {name: '广州', value: 90}], 163 [{name: '北京'}, {name: '大连', value: 80}], 164 [{name: '北京'}, {name: '南宁', value: 70}], 165 [{name: '北京'}, {name: '南昌', value: 60}], 166 [{name: '北京'}, {name: '拉萨', value: 50}], 167 [{name: '北京'}, {name: '长春', value: 40}], 168 [{name: '北京'}, {name: '包头', value: 30}], 169 [{name: '北京'}, {name: '重庆', value: 20}], 170 [{name: '北京'}, {name: '常州', value: 10}] 171 ]; 172 173 var SHData = [ 174 [{name: '上海'}, {name: '包头', value: 95}], 175 [{name: '上海'}, {name: '昆明', value: 90}], 176 [{name: '上海'}, {name: '广州', value: 80}], 177 [{name: '上海'}, {name: '郑州', value: 70}], 178 [{name: '上海'}, {name: '长春', value: 60}], 179 [{name: '上海'}, {name: '重庆', value: 50}], 180 [{name: '上海'}, {name: '长沙', value: 40}], 181 [{name: '上海'}, {name: '北京', value: 30}], 182 [{name: '上海'}, {name: '丹东', value: 20}], 183 [{name: '上海'}, {name: '大连', value: 10}] 184 ]; 185 186 var GZData = [ 187 [{name: '广州'}, {name: '福州', value: 95}], 188 [{name: '广州'}, {name: '太原', value: 90}], 189 [{name: '广州'}, {name: '长春', value: 80}], 190 [{name: '广州'}, {name: '重庆', value: 70}], 191 [{name: '广州'}, {name: '西安', value: 60}], 192 [{name: '广州'}, {name: '成都', value: 50}], 193 [{name: '广州'}, {name: '常州', value: 40}], 194 [{name: '广州'}, {name: '北京', value: 30}], 195 [{name: '广州'}, {name: '北海', value: 20}], 196 [{name: '广州'}, {name: '海口', value: 10}] 197 ]; 198 199 var planePath = 'path://M1705.06,1318.313v-89.254l-319.9-221.799l0.073-208.063c0.521-84.662-26.629-121.796-63.961-121.491c-37.332-0.305-64.482,36.829-63.961,121.491l0.073,208.063l-319.9,221.799v89.254l330.343-157.288l12.238,241.308l-134.449,92.931l0.531,42.034l175.125-42.917l175.125,42.917l0.531-42.034l-134.449-92.931l12.238-241.308L1705.06,1318.313z'; 200 201 var convertData = function (data) { 202 var res = []; 203 for (var i = 0; i < data.length; i++) { 204 var dataItem = data[i]; 205 var fromCoord = geoCoordMap[dataItem[0].name]; 206 var toCoord = geoCoordMap[dataItem[1].name]; 207 if (fromCoord && toCoord) { 208 res.push([{ 209 coord: fromCoord 210 }, { 211 coord: toCoord 212 }]); 213 } 214 } 215 return res; 216 }; 217 218 var color = ['#a6c84c', '#ffa022', '#46bee9']; 219 var series = []; 220 [['北京', BJData], ['上海', SHData], ['广州', GZData]].forEach(function (item, i) { 221 series.push({ 222 name: item[0] + ' Top10', 223 type: 'lines', 224 zlevel: 1, 225 effect: { 226 show: true, 227 period: 6, 228 trailLength: 0.7, 229 color: '#fff', 230 symbolSize: 3 231 }, 232 lineStyle: { 233 normal: { 234 color: color[i], 235 width: 0, 236 curveness: 0.2 237 } 238 }, 239 data: convertData(item[1]) 240 }, 241 { 242 name: item[0] + ' Top10', 243 type: 'lines', 244 zlevel: 2, 245 effect: { 246 show: true, 247 period: 6, 248 trailLength: 0, 249 symbol: planePath, 250 symbolSize: 15 251 }, 252 lineStyle: { 253 normal: { 254 color: color[i], 255 width: 1, 256 opacity: 0.4, 257 curveness: 0.2 258 } 259 }, 260 data: convertData(item[1]) 261 }, 262 { 263 name: item[0] + ' Top10', 264 type: 'effectScatter', 265 coordinateSystem: 'geo', 266 zlevel: 2, 267 rippleEffect: { 268 brushType: 'stroke' 269 }, 270 label: { 271 normal: { 272 show: true, 273 position: 'right', 274 formatter: '{b}' 275 } 276 }, 277 symbolSize: function (val) { 278 return val[2] / 8; 279 }, 280 itemStyle: { 281 normal: { 282 color: color[i] 283 } 284 }, 285 data: item[1].map(function (dataItem) { 286 return { 287 name: dataItem[1].name, 288 value: geoCoordMap[dataItem[1].name].concat([dataItem[1].value]) 289 }; 290 }) 291 }); 292 }); 293 294 option = { 295 // backgroundColor: '#404a59', 296 title: { 297 text: 'Leaflet扩展Echarts3之模拟迁徙', 298 subtext: 'Develop By WanderGIS', 299 left: 'center', 300 textStyle: { 301 color: '#fff' 302 } 303 }, 304 tooltip: { 305 trigger: 'item' 306 }, 307 legend: { 308 orient: 'vertical', 309 top: 'bottom', 310 left: 'left', 311 data: ['北京 Top10', '上海 Top10', '广州 Top10'], 312 textStyle: { 313 color: '#fff' 314 }, 315 selectedMode: 'single' 316 }, 317 geo: { 318 map: '', 319 label: { 320 emphasis: { 321 show: false 322 } 323 }, 324 roam: true, 325 itemStyle: { 326 normal: { 327 areaColor: '#323c48', 328 borderColor: '#404a59' 329 }, 330 emphasis: { 331 areaColor: '#2a333d' 332 } 333 } 334 }, 335 series: series 336 }; 337 // 使用刚指定的配置项和数据显示图表。 338 var echartlayer = new OpenLayers.Layer.EchartMapLayer("echartlayer",map,echarts,{ 339 option:option 340 }); 341 map.addLayer(echartlayer); 342 343 echartlayer.alwaysInRange = true; 344 345 </script> 346 </body>
注意:series的geo一定要设置,不然地图无法创建,map要置空,浏览器调试状态下会报错("Map not exists"),我们已经通过EchartMapLayer替代map的坐标转换功能;
EchartMapLayer的封装也是参考前面连接里的思路,代码如下:
1 OpenLayers.Layer.EchartMapLayer = OpenLayers.Class(OpenLayers.Layer, { 2 isBaseLayer : false, 3 alwaysInRange: true, 4 echart : null, 5 mapLayer : null, 6 initialize : function (name, map, echart, options) 7 { 8 var scope = this, echartdiv = document.createElement("div"), handler; 9 10 OpenLayers.Layer.prototype.initialize.apply(this, [name, options]); 11 12 echartdiv.style.cssText = "position:absolute;width:" + map.size.w + "px;height:" 13 + map.size.h + "px;"; 14 scope.div.appendChild(echartdiv); 15 scope.map = map; 16 scope.echartdiv = echartdiv; 17 scope.opacity = options.opacity; 18 scope.echart = echart; 19 scope.option = options.option; 20 handler = function (e) 21 { 22 scope.updateLayer(e); 23 }; 24 scope.updateLayer(); 25
26 map.events.register("moveend", this, handler); 27 }, 28 geoCoord2Pixel:function(geoCoord){ 29 var scope = this; 30 var lonLat = new OpenLayers.LonLat(geoCoord[0], geoCoord[1]); 31 var proj = new OpenLayers.Projection("EPSG:4326"); 32 lonLat.transform(proj, scope.map.getProjectionObject()); 33 34 var scrPt = scope.map.getPixelFromLonLat(lonLat); 35 var x = scrPt.x, 36 y = scrPt.y; 37 return [x,y]; 38 }, 39 updateLayer : function (e) 40 { 41 var scope = this; 42 var myChart = scope.echart.getInstanceByDom(scope.echartdiv); 43 if(!myChart){ 44 myChart = scope.echart.init(scope.echartdiv); 45 } 46 var orgXy, w, h; 47 if(e){ 48 orgXy = e.object.layerContainerOriginPx; 49 50 } 51 else{ 52 orgXy={x:0,y:0}; 53 } 54 w = scope.map.size.w; 55 h = scope.map.size.h; 56 scope.echartdiv.style.cssText = "position:absolute;top:"+(-orgXy.y)+"px;left:"+(-orgXy.x)+ 57 "px;width:" + w + "px;height:" + h + "px;"; 58 var ecOption = scope.getEcOption(); 59 myChart.setOption(ecOption); 60 }, 61 /** 62 *将echart的option转换 63 * @returns {*} 64 */ 65 getEcOption: function(){ 66 var scope = this; 67 scope._option = $.extend(true, {}, scope.option); //deep copy 68 var series = scope._option.series || {}; 69 70 for(var dex in series){ 71 var obj = series[dex]; 72 var type = obj.type; 73 if(type == "lines"){ 74 var datas = obj.data; 75 for(var i = 0; i < datas.length; i ++){ 76 var dataArray = datas[i]; 77 for(var j = 0; j < dataArray.length; j ++){ 78 var coords = dataArray[j]; 79 var coord = coords.coord; 80 var newGeo = scope._AddPos(coords); 81 scope._option.series[dex].data[i][j] = newGeo; 82 } 83 } 84 } 85 else if(type == "effectScatter"){ 86 var datas = obj.data; 87 for(var i = 0; i < datas.length; i ++){ 88 var dataArray = datas[i]; 89 var newObj = scope._AddPosEffect(dataArray); 90 scope._option.series[dex].data[i] = newObj; 91 } 92 93 } 94 95 } 96 return scope._option; 97 }, 98 _AddPos:function(coords){ 99 var scope = this; 100 var pos = scope.geoCoord2Pixel(coords.coord); 101 var newCoord = { 102 coord:[pos[0],pos[1]] 103 }; 104 return newCoord; 105 }, 106 _AddPosEffect:function(effectObj){ 107 var scope = this; 108 var pos = scope.geoCoord2Pixel(effectObj.value); 109 var newObj = { 110 name: effectObj.name, 111 value: [ 112 pos[0], 113 pos[1], 114 effectObj.value[2] 115 ] 116 }; 117 return newObj; 118 }, 119 destroy : function () 120 { 121 OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments); 122 }, 123 CLASS_NAME : "OpenLayers.Layer.EchartMapLayer" 124 } 125 );
注意:getEcOption内的坐标转换需根据不同的series包含的数据,分层查找;
效果图,OSM地图颜色偏白,可自行修改chart的配色。