0%

【ArcGIS JS API + eCharts系列】实现二、三维迁徙图的绘制

本文主要介绍使用ArcGIS JS API 4.14和eCharts 4.7.0来实现在地图上绘制二维图表中的迁徙图的实现步骤,包括二维和三维。

概述

上一篇文章通过纯前端的方式实现了ArcGIS JS API和eCharts的普通二维图表绘制,因为这些图表绘制其实是跟地理坐标无关的,只需要设置图表的位置即可,所以仅仅用了纯前端的方式去实现。这篇文章通过参考dGIS大佬的文章,重构了EchartsLayer.js文件(因为网上所有关于EchartsLayer.js文件的代码全部是编译过的,阅读起来很不友好,并且有些EchartsLayer.js扩展文件仅仅支持到ArcGIS JS API 4.6版本,对于ArcGIS JS API 4.14这样的高版本切换还是有一些问题),使其支持最新版的ArcGIS API for JavaScript 4.14和eCharts 4版本,实现了在ArcGIS的底图上使其能够绘制二维和三维的迁徙图,我们先来看一下效果:
二维迁徙图
三维迁徙图

实现思路

迁徙图、散点图这种图表跟地理坐标关系紧密,所以仅仅通过二维普通图表绘制的方式是无法实现这类图表绘制的,所以就需要我们来扩展eCharts的相关功能,使其能够够结合最新版的ArcGIS JS API来完成地图上这类图表的绘制,eCharts官网也提供了相应的扩展插件,但这种插件并不能很好地支持我们ArcGIS JS API的高版本,所以我们在这篇文章里直接扩展了一个图层类,下面是具体的实现思路:
实现ArcGIS JS API和eCharts的结合,最最关键的是要实现两个插件库里的坐标系转换,这是重点,只要搞清楚了这一点,我们完全可以脱离地图API库的束缚,理论上可以实现eCharts跟任意地图库的结合。在此处转换坐标时我们使用了eCharts提供的registerCoordinateSystem方法,通过这个方法我们注册了一个名为”arcgis”的坐标系,里面对eCharts中的dataToPoint、pointToData等方法进行了重写,然后将这些所有内容封装为了一个EchartsLayer图层类。至于这个文件的源码,文章结尾会提供,接下来我们看一下具体的实现步骤。

实现步骤

1、本文所用的demo是基于React框架搭建的,所以我们首先基于React框架搭建一个初始化项目,然后改写src目录下的App.js这个主文件,实例化出一张二维地图,这中间用到了esri-loader插件,具体的实现过程可查看我的这篇文章【番外】 React中使用ArcGIS JS API 4.14开发,里面有具体的实现步骤。
2、通过上述操作实例化完一张二维地图后,我们接下来就要进行迁徙图的绘制操作了,在开始之前我们需要一些数据,首先是迁徙途中所要用到的各个行政区划的省会城市坐标,是一份JSON文件,源文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
let GeoCodingData = {
   '海门': [121.15, 31.89],
   '鄂尔多斯': [109.781327, 39.608266],
   '招远': [120.38, 37.35],
   '舟山': [122.207216, 29.985295],
   '齐齐哈尔': [123.97, 47.33],
   '盐城': [120.13, 33.38],
   '赤峰': [118.87, 42.28],
   '青岛': [120.33, 36.07],
   '乳山': [121.52, 36.89],
   '金昌': [102.188043, 38.520089],
   '泉州': [118.58, 24.93],
   '莱西': [120.53, 36.86],
   '日照': [119.46, 35.42],
   '胶南': [119.97, 35.88],
   '南通': [121.05, 32.08],
   '拉萨': [91.11, 29.97],
   '云浮': [112.02, 22.93],
   '梅州': [116.1, 24.55],
   '文登': [122.05, 37.2],
   '上海': [121.48, 31.22],
   '攀枝花': [101.718637, 26.582347],
   '威海': [122.1, 37.5],
   '承德': [117.93, 40.97],
   '厦门': [118.1, 24.46],
   '汕尾': [115.375279, 22.786211],
   '潮州': [116.63, 23.68],
   '丹东': [124.37, 40.13],
   '太仓': [121.1, 31.45],
   '曲靖': [103.79, 25.51],
   '烟台': [121.39, 37.52],
   '福州': [119.3, 26.08],
   '瓦房店': [121.979603, 39.627114],
   '即墨': [120.45, 36.38],
   '抚顺': [123.97, 41.97],
   '玉溪': [102.52, 24.35],
   '张家口': [114.87, 40.82],
   '阳泉': [113.57, 37.85],
   '莱州': [119.942327, 37.177017],
   '湖州': [120.1, 30.86],
   '汕头': [116.69, 23.39],
   '昆山': [120.95, 31.39],
   '宁波': [121.56, 29.86],
   '湛江': [110.359377, 21.270708],
   '揭阳': [116.35, 23.55],
   '荣成': [122.41, 37.16],
   '连云港': [119.16, 34.59],
   '葫芦岛': [120.836932, 40.711052],
   '常熟': [120.74, 31.64],
   '东莞': [113.75, 23.04],
   '河源': [114.68, 23.73],
   '淮安': [119.15, 33.5],
   '泰州': [119.9, 32.49],
   '南宁': [108.33, 22.84],
   '营口': [122.18, 40.65],
   '惠州': [114.4, 23.09],
   '江阴': [120.26, 31.91],
   '蓬莱': [120.75, 37.8],
   '韶关': [113.62, 24.84],
   '嘉峪关': [98.289152, 39.77313],
   '广州': [113.23, 23.16],
   '延安': [109.47, 36.6],
   '太原': [112.53, 37.87],
   '清远': [113.01, 23.7],
   '中山': [113.38, 22.52],
   '昆明': [102.73, 25.04],
   '寿光': [118.73, 36.86],
   '盘锦': [122.070714, 41.119997],
   '长治': [113.08, 36.18],
   '深圳': [114.07, 22.62],
   '珠海': [113.52, 22.3],
   '宿迁': [118.3, 33.96],
   '咸阳': [108.72, 34.36],
   '铜川': [109.11, 35.09],
   '平度': [119.97, 36.77],
   '佛山': [113.11, 23.05],
   '海口': [110.35, 20.02],
   '江门': [113.06, 22.61],
   '章丘': [117.53, 36.72],
   '肇庆': [112.44, 23.05],
   '大连': [121.62, 38.92],
   '临汾': [111.5, 36.08],
   '吴江': [120.63, 31.16],
   '石嘴山': [106.39, 39.04],
   '沈阳': [123.38, 41.8],
   '苏州': [120.62, 31.32],
   '茂名': [110.88, 21.68],
   '嘉兴': [120.76, 30.77],
   '长春': [125.35, 43.88],
   '胶州': [120.03336, 36.264622],
   '银川': [106.27, 38.47],
   '张家港': [120.555821, 31.875428],
   '三门峡': [111.19, 34.76],
   '锦州': [121.15, 41.13],
   '南昌': [115.89, 28.68],
   '柳州': [109.4, 24.33],
   '三亚': [109.511909, 18.252847],
   '自贡': [104.778442, 29.33903],
   '吉林': [126.57, 43.87],
   '阳江': [111.95, 21.85],
   '泸州': [105.39, 28.91],
   '西宁': [101.74, 36.56],
   '宜宾': [104.56, 29.77],
   '呼和浩特': [111.65, 40.82],
   '成都': [104.06, 30.67],
   '大同': [113.3, 40.12],
   '镇江': [119.44, 32.2],
   '桂林': [110.28, 25.29],
   '张家界': [110.479191, 29.117096],
   '宜兴': [119.82, 31.36],
   '北海': [109.12, 21.49],
   '西安': [108.95, 34.27],
   '金坛': [119.56, 31.74],
   '东营': [118.49, 37.46],
   '牡丹江': [129.58, 44.6],
   '遵义': [106.9, 27.7],
   '绍兴': [120.58, 30.01],
   '扬州': [119.42, 32.39],
   '常州': [119.95, 31.79],
   '潍坊': [119.1, 36.62],
   '重庆': [106.54, 29.59],
   '台州': [121.420757, 28.656386],
   '南京': [118.78, 32.04],
   '滨州': [118.03, 37.36],
   '贵阳': [106.71, 26.57],
   '无锡': [120.29, 31.59],
   '本溪': [123.73, 41.3],
   '克拉玛依': [84.77, 45.59],
   '渭南': [109.5, 34.52],
   '马鞍山': [118.48, 31.56],
   '宝鸡': [107.15, 34.38],
   '焦作': [113.21, 35.24],
   '句容': [119.16, 31.95],
   '北京': [116.46, 39.92],
   '徐州': [117.2, 34.26],
   '衡水': [115.72, 37.72],
   '包头': [110, 40.58],
   '绵阳': [104.73, 31.48],
   '乌鲁木齐': [87.68, 43.77],
   '枣庄': [117.57, 34.86],
   '杭州': [120.19, 30.26],
   '淄博': [118.05, 36.78],
   '鞍山': [122.85, 41.12],
   '溧阳': [119.48, 31.43],
   '库尔勒': [86.06, 41.68],
   '安阳': [114.35, 36.1],
   '开封': [114.35, 34.79],
   '济南': [117, 36.65],
   '德阳': [104.37, 31.13],
   '温州': [120.65, 28.01],
   '九江': [115.97, 29.71],
   '邯郸': [114.47, 36.6],
   '临安': [119.72, 30.23],
   '兰州': [103.73, 36.03],
   '沧州': [116.83, 38.33],
   '临沂': [118.35, 35.05],
   '南充': [106.110698, 30.837793],
   '天津': [117.2, 39.13],
   '富阳': [119.95, 30.07],
   '泰安': [117.13, 36.18],
   '诸暨': [120.23, 29.71],
   '郑州': [113.65, 34.76],
   '哈尔滨': [126.63, 45.75],
   '聊城': [115.97, 36.45],
   '芜湖': [118.38, 31.33],
   '唐山': [118.02, 39.63],
   '平顶山': [113.29, 33.75],
   '邢台': [114.48, 37.05],
   '德州': [116.29, 37.45],
   '济宁': [116.59, 35.38],
   '荆州': [112.239741, 30.335165],
   '宜昌': [111.3, 30.7],
   '义乌': [120.06, 29.32],
   '丽水': [119.92, 28.45],
   '洛阳': [112.44, 34.7],
   '秦皇岛': [119.57, 39.95],
   '株洲': [113.16, 27.83],
   '石家庄': [114.48, 38.03],
   '莱芜': [117.67, 36.19],
   '常德': [111.69, 29.05],
   '保定': [115.48, 38.85],
   '湘潭': [112.91, 27.87],
   '金华': [119.64, 29.12],
   '岳阳': [113.09, 29.37],
   '长沙': [113, 28.21],
   '衢州': [118.88, 28.97],
   '廊坊': [116.7, 39.53],
   '菏泽': [115.480656, 35.23375],
   '合肥': [117.27, 31.86],
   '武汉': [114.31, 30.52],
   '大庆': [125.03, 46.58]
};

export default GeoCodingData;

在此处因为数据量有点大,所以我将它单独提出来作为一个文件,放在项目目录的data文件夹下,然后需要在我们的组件中定义state,里面存放一些迁徙图各个中心点的数据和后期创建地图后绘制图表时所要用到的数据,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    state = {
       BJData: [
          [{name:'北京'}, {name:'上海',value:95}],
          [{name:'北京'}, {name:'广州',value:90}],
          [{name:'北京'}, {name:'大连',value:80}],
          [{name:'北京'}, {name:'南宁',value:70}],
          [{name:'北京'}, {name:'南昌',value:60}],
          [{name:'北京'}, {name:'拉萨',value:50}],
          [{name:'北京'}, {name:'长春',value:40}],
          [{name:'北京'}, {name:'包头',value:30}],
          [{name:'北京'}, {name:'重庆',value:20}],
          [{name:'北京'}, {name:'常州',value:10}]
      ],
       SHData: [
          [{name:'上海'},{name:'包头',value:95}],
          [{name:'上海'},{name:'昆明',value:90}],
          [{name:'上海'},{name:'广州',value:80}],
          [{name:'上海'},{name:'郑州',value:70}],
          [{name:'上海'},{name:'长春',value:60}],
          [{name:'上海'},{name:'重庆',value:50}],
          [{name:'上海'},{name:'长沙',value:40}],
          [{name:'上海'},{name:'北京',value:30}],
          [{name:'上海'},{name:'丹东',value:20}],
          [{name:'上海'},{name:'大连',value:10}]
      ],
       GZData: [
          [{name:'广州'},{name:'福州',value:95}],
          [{name:'广州'},{name:'太原',value:90}],
          [{name:'广州'},{name:'长春',value:80}],
          [{name:'广州'},{name:'重庆',value:70}],
          [{name:'广州'},{name:'西安',value:60}],
          [{name:'广州'},{name:'成都',value:50}],
          [{name:'广州'},{name:'常州',value:40}],
          [{name:'广州'},{name:'北京',value:30}],
          [{name:'广州'},{name:'北海',value:20}],
          [{name:'广州'},{name:'海口',value:10}]
      ],
       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',       //迁徙图上的移动样式,目前是默认的小飞机,后期可以自己改
       color: ['#F4EF8D', '#323296', '#CB4743'],      //各个迁徙中心点的航线颜色
       series: [],      //eCharts绘制时所需的配置信息
       mapview: null,   //实例化地图后存放地图视图

2、定义完上述的基础数据之后,我们接下来进行迁徙图的绘制,这中间其实就是配置一些绘制迁徙图时所要用到的eCharts图表的配置信息,我们将这些配置信息全部压到state中的series数组中去,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    _initCharts=() => {
       const _self = this;
       let placeCenter = [
          ['北京', this.state.BJData],
          ['上海', this.state.SHData],
          ['广州', this.state.GZData]
      ];

       placeCenter.map((value, key) => {
           _self.state.series.push({
               name: value[0] + 'Top10',
               type: 'lines',
               coordinateSystem: 'arcgis',
               zlevel: 1,
               effect: {
                   show: true,
                   period: 6,
                   trailLength: 0.7,
                   color: '#fff',
                   symbolSize: 3
              },
               lineStyle: {
                   normal: {
                       color: _self.state.color[key],
                       width: 0,
                       curveness: 0.2
                  }
              },
               data: _self._convertData(value[1])
          }, {
               name: value[0] + ' Top10',
               type: 'lines',
               coordinateSystem: 'arcgis',
               zlevel: 2,
               symbol: ['none', 'arrow'],
               symbolSize: 10,
               effect: {
                   show: true,
                   period: 6,
                   trailLength: 0,
                   symbol: _self.state.planePath,
                   symbolSize: 15
              },
               lineStyle: {
                 normal: {
                   color: _self.state.color[key],
                   width: 1,
                   opacity: 0.6,
                   curveness: 0.2
                }
              },
               data: _self._convertData(value[1])
          }, {
               name: value[0] + ' Top10',
               type: 'effectScatter',
               coordinateSystem: 'arcgis',
               zlevel: 2,
               rippleEffect: {
                   brushType: 'stroke'
              },
               label: {
                   normal: {
                       show: true,
                       position: 'left',
                       formatter: '{b}'
                  }
              },
               symbolSize: function (val) {
                   return val[2] / 8;
              },
               itemStyle: {
                   normal: {
                       color: _self.state.color[key]
                  }
              },
               data: value[1].map(function(dataItem) {
                   return {
                       name: dataItem[1].name,
                       value: GeoCodingData[dataItem[1].name].concat([dataItem[1].value])
                  };
              })
          });
      });
  }

上面过程中用到了_convertData方法,主要用于数据的转换,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    _convertData=(data) => {
       let res = [];
           for (var i = 0; i < data.length; i++) {
             var dataItem = data[i];
             var fromCoord = GeoCodingData[dataItem[0].name];
             var toCoord = GeoCodingData[dataItem[1].name];
             if (fromCoord && toCoord) {
               res.push({
                 fromName: dataItem[0].name,
                 toName: dataItem[1].name,
                 coords: [fromCoord, toCoord],
                 value: dataItem[1].value
              });
            }
          }
       return res;
  }

3、经过上述的操作,我们已经完成了底图的绘制和迁徙图的配置信息实例化,接下来就进行迁徙图的绘制操作。绘制过程要监听底图实例化的when方法,等待地图实例化完成之后再进行绘制,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
view.when(function() {
_self.state.mapview = view;
_self._drawCharts();
});
   _drawCharts=() => {
       const _self = this;
       const options = {
           url: 'https://js.arcgis.com/4.14/dojo/dojo.js',
      };

       loadModules([
           'http://localhost/test/EchartsLayer.min.js'
      ], options).then(([
           echartsLayer
      ]) => {
           console.log(_self.state.mapview)
           //_self.state.mapview.when(function(){
               let chart = new echartsLayer(_self.state.mapview);
               let option = {
                   title: {
                       text: 'ArcGIS API for Javascript4.14扩展Echarts4之模拟迁徙',
                       subtext: 'Develop By X北辰北',
                       left: 'center',
                       textStyle: {
                           color: '#fff'
                      }
                  },
                   series: _self.state.series
              };
               chart.setChartOption(option);
           //});
      }
      ).catch((err) => {
           console.log('图表绘制失败,' + err);
      });
  }

4、经过以上操作后我们就完成了迁徙图的绘制,在这里要注意的是,绘制迁徙图时要调用我们扩展的EchartsLayer图层类,这个图层类我们只需要将它放到本级服务器目录下即可,或者放在demo的项目文件夹下面,然后我们引入dojo文件的时候不用上述代码里的那样引用官网的dojo文件,而是自己下载dojo文件到demo项目里,然后在引入EchartsLayer图层类之前通过dojoConfig类配置下引用路径就好了。
5、以上过程完成了二维场景下迁徙图的绘制,三维场景下的绘制其实很简单,我们只需要将视图层换成三维就可以了,代码如下:、

1
2
3
4
5
6
let view = new SceneView({
container: "mapview",
map: map,
scale: 50000000,
center: [107.246152,34.414465]
});

6、以上就是迁徙图在二维和三维下的绘制过程。

总结

本文主要介绍下ArcGIS JS API高版本和eCharts 4版本的结合使用,在这篇文章里最重要的是我们实现了两个图表库中的坐标系转换工作,只要完成了这个步骤,那接下来的绘制其实跟eCharts的普通绘制是相差不大的,后期我会继续更新EchartsLayer这个图层类,迎合最新版的ArcGIS JS API,欢迎大家持续关注。

附:

App.js全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import React,{Component} from 'react';
import { loadModules } from 'esri-loader';
import GeoCodingData from './data/geoCodingData';
import './App.css';

class App extends Component {

   state = {
       BJData: [
          [{name:'北京'}, {name:'上海',value:95}],
          [{name:'北京'}, {name:'广州',value:90}],
          [{name:'北京'}, {name:'大连',value:80}],
          [{name:'北京'}, {name:'南宁',value:70}],
          [{name:'北京'}, {name:'南昌',value:60}],
          [{name:'北京'}, {name:'拉萨',value:50}],
          [{name:'北京'}, {name:'长春',value:40}],
          [{name:'北京'}, {name:'包头',value:30}],
          [{name:'北京'}, {name:'重庆',value:20}],
          [{name:'北京'}, {name:'常州',value:10}]
      ],
       SHData: [
          [{name:'上海'},{name:'包头',value:95}],
          [{name:'上海'},{name:'昆明',value:90}],
          [{name:'上海'},{name:'广州',value:80}],
          [{name:'上海'},{name:'郑州',value:70}],
          [{name:'上海'},{name:'长春',value:60}],
          [{name:'上海'},{name:'重庆',value:50}],
          [{name:'上海'},{name:'长沙',value:40}],
          [{name:'上海'},{name:'北京',value:30}],
          [{name:'上海'},{name:'丹东',value:20}],
          [{name:'上海'},{name:'大连',value:10}]
      ],
       GZData: [
          [{name:'广州'},{name:'福州',value:95}],
          [{name:'广州'},{name:'太原',value:90}],
          [{name:'广州'},{name:'长春',value:80}],
          [{name:'广州'},{name:'重庆',value:70}],
          [{name:'广州'},{name:'西安',value:60}],
          [{name:'广州'},{name:'成都',value:50}],
          [{name:'广州'},{name:'常州',value:40}],
          [{name:'广州'},{name:'北京',value:30}],
          [{name:'广州'},{name:'北海',value:20}],
          [{name:'广州'},{name:'海口',value:10}]
      ],
       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',
       color: ['#F4EF8D', '#323296', '#CB4743'],
       series: [],
       mapview: null,
  };

   componentDidMount=() => {
       this._createMapview();
       this._initCharts();
  }

   //创建二维地图
   _createMapview=() => {
       const _self = this;
       const options = {
           url: 'https://js.arcgis.com/4.14/',
           css: 'https://js.arcgis.com/4.14/esri/themes/light/main.css'
      };

       loadModules(['esri/Map',
           'esri/Basemap',
           'esri/layers/TileLayer',
           'esri/views/MapView',
           'esri/views/SceneView',
      ], options).then(([
           Map,
           Basemap,
           TileLayer,
           MapView,
           SceneView,
      ]) => {
                   let basemap = new Basemap({
                       baseLayers: [
                           new TileLayer({
                               url: "http://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer",
                               title: "Basemap"
                          })
                      ],
                       title: "basemap",
                       id: "basemap"
                  });
                   let map = new Map({
                       basemap: basemap
                  });
                   // let view = new MapView({
                   //     container: "mapview",
                   //     map: map,
                   //     zoom: 5,
                   //     center: [107.246152,34.414465]
                   // });
                   let view = new SceneView({
                       container: "mapview",
                       map: map,
                       scale: 50000000,
                       center: [107.246152,34.414465]
                  });
                   
                   view.when(function() {
                       _self.state.mapview = view;
                       _self._drawCharts();
                  });
          }
      ).catch((err) => {
           console.log('底图创建失败,' + err);
      });
  }

   _initCharts=() => {
       const _self = this;
       let placeCenter = [
          ['北京', this.state.BJData],
          ['上海', this.state.SHData],
          ['广州', this.state.GZData]
      ];

       placeCenter.map((value, key) => {
           _self.state.series.push({
               name: value[0] + 'Top10',
               type: 'lines',
               coordinateSystem: 'arcgis',
               zlevel: 1,
               effect: {
                   show: true,
                   period: 6,
                   trailLength: 0.7,
                   color: '#fff',
                   symbolSize: 3
              },
               lineStyle: {
                   normal: {
                       color: _self.state.color[key],
                       width: 0,
                       curveness: 0.2
                  }
              },
               data: _self._convertData(value[1])
          }, {
               name: value[0] + ' Top10',
               type: 'lines',
               coordinateSystem: 'arcgis',
               zlevel: 2,
               symbol: ['none', 'arrow'],
               symbolSize: 10,
               effect: {
                   show: true,
                   period: 6,
                   trailLength: 0,
                   symbol: _self.state.planePath,
                   symbolSize: 15
              },
               lineStyle: {
                 normal: {
                   color: _self.state.color[key],
                   width: 1,
                   opacity: 0.6,
                   curveness: 0.2
                }
              },
               data: _self._convertData(value[1])
          }, {
               name: value[0] + ' Top10',
               type: 'effectScatter',
               coordinateSystem: 'arcgis',
               zlevel: 2,
               rippleEffect: {
                   brushType: 'stroke'
              },
               label: {
                   normal: {
                       show: true,
                       position: 'left',
                       formatter: '{b}'
                  }
              },
               symbolSize: function (val) {
                   return val[2] / 8;
              },
               itemStyle: {
                   normal: {
                       color: _self.state.color[key]
                  }
              },
               data: value[1].map(function(dataItem) {
                   return {
                       name: dataItem[1].name,
                       value: GeoCodingData[dataItem[1].name].concat([dataItem[1].value])
                  };
              })
          });
      });
  }

   _convertData=(data) => {
       let res = [];
           for (var i = 0; i < data.length; i++) {
             var dataItem = data[i];
             var fromCoord = GeoCodingData[dataItem[0].name];
             var toCoord = GeoCodingData[dataItem[1].name];
             if (fromCoord && toCoord) {
               res.push({
                 fromName: dataItem[0].name,
                 toName: dataItem[1].name,
                 coords: [fromCoord, toCoord],
                 value: dataItem[1].value
              });
            }
          }
       return res;
  }

   _drawCharts=() => {
       const _self = this;
       const options = {
           url: 'https://js.arcgis.com/4.14/dojo/dojo.js',
      };

       loadModules([
           'http://localhost/test/EchartsLayer.min.js'
      ], options).then(([
           echartsLayer
      ]) => {
           console.log(_self.state.mapview)
           //_self.state.mapview.when(function(){
               let chart = new echartsLayer(_self.state.mapview);
               let option = {
                   title: {
                       text: 'ArcGIS API for Javascript4.14扩展Echarts4之模拟迁徙',
                       subtext: 'Develop By X北辰北',
                       left: 'center',
                       textStyle: {
                           color: '#fff'
                      }
                  },
                   series: _self.state.series
              };
               chart.setChartOption(option);
           //});
      }
      ).catch((err) => {
           console.log('底图创建失败,' + err);
      });
  }

   render() {
       return (
           <div className="mainview">
               <div id="mapview"></div>
           </div>
        );
  }
}

export default App;

EchartsLayer.min.js全部代码

1
var _0x4564=['prototype','setMapOffset','dataToPoint','point','toScreen','pointToData','toMap','getViewRect','BoundingRect','getRoamTransform','dojo/_base/declare','dojo/_base/lang','esri/geometry/Point','esri/geometry/SpatialReference','EchartsglLayer','registerCoordinateSystem','arcgis','getE3CoordinateSystem','init','setBaseMap','createLayer','view','chartOption','setCharts','box','visible','hidden','chart','off','undefined','extent','xAxis','xmin','xmax','yAxis','ymin','ymax','setOption','animation','createElement','div','setAttribute','echartsData','name','style','width','height','position','absolute','top','left','getElementsByClassName','esri-view-surface','appendChild','startMapEventListeners','outerHTML','originLyr','features','screenData','map_DragStart_Listener','remove','map_DragEnd_Listener','map_ZoomStart_Listener','map_ZoomEnd_Listener','map_ExtentChange_Listener','watch','hitch','resize','rotation','map','_mapOffset','create','eachSeries','get','coordinateSystem','getDimensionsInfo','dimensions'];(function(_0x4ea369,_0x173297){var _0x432a1a=function(_0x3b4d7a){while(--_0x3b4d7a){_0x4ea369['push'](_0x4ea369['shift']());}};_0x432a1a(++_0x173297);}(_0x4564,0xf1));var _0x1824=function(_0x20e690,_0x5f0396){_0x20e690=_0x20e690-0x0;var _0x841fe2=_0x4564[_0x20e690];return _0x841fe2;};define([_0x1824('0x0'),_0x1824('0x1'),_0x1824('0x2'),_0x1824('0x3')],function(_0x4156fb,_0x59c3eb,_0x275378,_0x4d54b1){return _0x4156fb(_0x1824('0x4'),null,{'name':_0x1824('0x4'),'view':null,'box':null,'chart':null,'chartOption':null,'visible':!![],'constructor':function(_0x27b7d3,_0x649a95){echarts[_0x1824('0x5')](_0x1824('0x6'),this[_0x1824('0x7')](_0x27b7d3));this[_0x1824('0x8')](_0x27b7d3,_0x649a95);},'init':function(_0x3a80a9,_0x5617d3){this[_0x1824('0x9')](_0x3a80a9);this[_0x1824('0xa')]();},'setBaseMap':function(_0x3ddf37){this[_0x1824('0xb')]=_0x3ddf37;},'setChartOption':function(_0x497153){this[_0x1824('0xc')]=_0x497153;this[_0x1824('0xd')]();},'setVisible':function(_0x36aa18){if(!this[_0x1824('0xe')]||this[_0x1824('0xf')]===_0x36aa18)return;this[_0x1824('0xe')][_0x1824('0x10')]=!_0x36aa18;this[_0x1824('0xf')]=_0x36aa18;_0x36aa18===!![]&&setCharts();},'refreshBegin':function(){this[_0x1824('0xe')][_0x1824('0x10')]=!![];},'refreshing':function(){setCharts();},'refreshEnd':function(){this[_0x1824('0xe')][_0x1824('0x10')]=![];},'on':function(_0x5dd691,_0x472109,_0x4b90b9){this[_0x1824('0x11')]['on'](_0x5dd691,_0x472109,_0x4b90b9);},'off':function(_0x25e82f,_0x44fdf2,_0x3cd39d){this[_0x1824('0x11')][_0x1824('0x12')](_0x25e82f,_0x44fdf2,_0x3cd39d);},'map_DragStart_Listener':null,'map_DragEnd_Listener':null,'map_ZoomStart_Listener':null,'map_ZoomEnd_Listener':null,'map_ExtentChange_Listener':null,'map_click_Listener':null,'setCharts':function(){if(!this[_0x1824('0xf')])return;if(this[_0x1824('0xc')]==null||this[_0x1824('0xc')]==_0x1824('0x13'))return;let _0x50f53f=this[_0x1824('0xb')][_0x1824('0x14')];this[_0x1824('0xc')][_0x1824('0x15')]={'show':![],'min':_0x50f53f[_0x1824('0x16')],'max':_0x50f53f[_0x1824('0x17')]};this[_0x1824('0xc')][_0x1824('0x18')]={'show':![],'min':_0x50f53f[_0x1824('0x19')],'max':_0x50f53f[_0x1824('0x1a')]};this[_0x1824('0x11')][_0x1824('0x1b')](this[_0x1824('0xc')]);this[_0x1824('0xc')][_0x1824('0x1c')]=![];},'createLayer':function(){let _0x56973d=this[_0x1824('0xe')]=document[_0x1824('0x1d')](_0x1824('0x1e'));_0x56973d[_0x1824('0x1f')]('id',_0x1824('0x20'));_0x56973d[_0x1824('0x1f')](_0x1824('0x21'),_0x1824('0x20'));_0x56973d[_0x1824('0x22')][_0x1824('0x23')]=this[_0x1824('0xb')][_0x1824('0x23')]+'px';_0x56973d[_0x1824('0x22')][_0x1824('0x24')]=this[_0x1824('0xb')][_0x1824('0x24')]+'px';_0x56973d[_0x1824('0x22')][_0x1824('0x25')]=_0x1824('0x26');_0x56973d[_0x1824('0x22')][_0x1824('0x27')]=0x0;_0x56973d[_0x1824('0x22')][_0x1824('0x28')]=0x0;let _0x22f992=document[_0x1824('0x29')](_0x1824('0x2a'))[0x0];_0x22f992[_0x1824('0x2b')](_0x56973d);this[_0x1824('0x11')]=echarts[_0x1824('0x8')](_0x56973d);this[_0x1824('0x2c')]();},'removeLayer':function(){this[_0x1824('0xe')][_0x1824('0x2d')]='';this[_0x1824('0xb')]=null;this[_0x1824('0xe')]=null;this[_0x1824('0x2e')]=null;this[_0x1824('0x2f')]=null;this[_0x1824('0x30')]=[];this[_0x1824('0x11')]=null;this[_0x1824('0xc')]=null;this[_0x1824('0x31')][_0x1824('0x32')]();this[_0x1824('0x33')][_0x1824('0x32')]();this[_0x1824('0x34')][_0x1824('0x32')]();this[_0x1824('0x35')][_0x1824('0x32')]();this[_0x1824('0x36')][_0x1824('0x32')]();},'startMapEventListeners':function(){let _0x576d14=this[_0x1824('0xb')];_0x576d14[_0x1824('0x37')](_0x1824('0x14'),_0x59c3eb[_0x1824('0x38')](this,function(){if(!this[_0x1824('0xf')])return;this[_0x1824('0xd')]();this[_0x1824('0x11')][_0x1824('0x39')]();this[_0x1824('0xe')][_0x1824('0x10')]=![];}));_0x576d14[_0x1824('0x37')](_0x1824('0x3a'),_0x59c3eb[_0x1824('0x38')](this,function(){if(!this[_0x1824('0xf')])return;this[_0x1824('0xd')]();this[_0x1824('0x11')][_0x1824('0x39')]();this[_0x1824('0xe')][_0x1824('0x10')]=![];}));},'getE3CoordinateSystem':function(_0x56f41a){var _0x4504c9=function _0x4504c9(_0x180267){this[_0x1824('0x3b')]=_0x180267;this[_0x1824('0x3c')]=[0x0,0x0];};_0x4504c9[_0x1824('0x3d')]=function(_0x1a4547){_0x1a4547[_0x1824('0x3e')](function(_0x17e9bb){if(_0x17e9bb[_0x1824('0x3f')](_0x1824('0x40'))===_0x1824('0x6')){_0x17e9bb[_0x1824('0x40')]=new _0x4504c9(_0x56f41a);}});};_0x4504c9[_0x1824('0x41')]=function(){return['x','y'];};_0x4504c9[_0x1824('0x42')]=['x','y'];_0x4504c9[_0x1824('0x43')][_0x1824('0x42')]=['x','y'];_0x4504c9[_0x1824('0x43')][_0x1824('0x44')]=function setMapOffset(_0xeffdb8){this[_0x1824('0x3c')]=_0xeffdb8;};_0x4504c9[_0x1824('0x43')][_0x1824('0x45')]=function dataToPoint(_0x209327){var _0x2755d4={'type':_0x1824('0x46'),'x':_0x209327[0x0],'y':_0x209327[0x1],'spatialReference':new _0x4d54b1(0x10e6)};var _0x3676a6=_0x56f41a[_0x1824('0x47')](_0x2755d4);var _0x52b765=this[_0x1824('0x3c')];return[_0x3676a6['x']-_0x52b765[0x0],_0x3676a6['y']-_0x52b765[0x1]];};_0x4504c9[_0x1824('0x43')][_0x1824('0x48')]=function pointToData(_0x5d9368){var _0x4282c5=this[_0x1824('0x3c')];var _0x3a367d={'x':_0x5d9368[0x0]+_0x4282c5[0x0],'y':_0x5d9368[0x1]+_0x4282c5[0x1]};var _0x3a9399=_0x56f41a[_0x1824('0x49')](_0x3a367d);return[_0x3a9399['x'],_0x3a9399['y']];};_0x4504c9[_0x1824('0x43')][_0x1824('0x4a')]=function getViewRect(){return new graphic[(_0x1824('0x4b'))](0x0,0x0,this[_0x1824('0x3b')][_0x1824('0x23')],this[_0x1824('0x3b')][_0x1824('0x24')]);};_0x4504c9[_0x1824('0x43')][_0x1824('0x4c')]=function getRoamTransform(){return matrix[_0x1824('0x3d')]();};return _0x4504c9;}});});