xref: /aosp_15_r20/external/ot-br-posix/src/web/web-service/frontend/res/js/app.js (revision 4a64e381480ef79f0532b2421e44e6ee336b8e0d)
1/*
2 *    Copyright (c) 2017, The OpenThread Authors.
3 *    All rights reserved.
4 *
5 *    Redistribution and use in source and binary forms, with or without
6 *    modification, are permitted provided that the following conditions are met:
7 *    1. Redistributions of source code must retain the above copyright
8 *       notice, this list of conditions and the following disclaimer.
9 *    2. Redistributions in binary form must reproduce the above copyright
10 *       notice, this list of conditions and the following disclaimer in the
11 *       documentation and/or other materials provided with the distribution.
12 *    3. Neither the name of the copyright holder nor the
13 *       names of its contributors may be used to endorse or promote products
14 *       derived from this software without specific prior written permission.
15 *
16 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 *    POSSIBILITY OF SUCH DAMAGE.
27 */
28
29(function() {
30    angular
31        .module('StarterApp', ['ngMaterial', 'ngMessages'])
32        .controller('AppCtrl', AppCtrl)
33        .service('sharedProperties', function() {
34            var index = 0;
35            var networkInfo;
36
37            return {
38                getIndex: function() {
39                    return index;
40                },
41                setIndex: function(value) {
42                    index = value;
43                },
44                getNetworkInfo: function() {
45                    return networkInfo;
46                },
47                setNetworkInfo: function(value) {
48                    networkInfo = value
49                },
50            };
51        });
52
53    function AppCtrl($scope, $http, $mdDialog, $interval, sharedProperties) {
54        $scope.menu = [{
55                title: 'Home',
56                icon: 'home',
57                show: true,
58            },
59            {
60                title: 'Join',
61                icon: 'add_circle_outline',
62                show: false,
63            },
64            {
65                title: 'Form',
66                icon: 'open_in_new',
67                show: false,
68            },
69            {
70                title: 'Status',
71                icon: 'info_outline',
72                show: false,
73            },
74            {
75                title: 'Settings',
76                icon: 'settings',
77                show: false,
78            },
79            {
80                title: 'Commission',
81                icon: 'add_circle_outline',
82                show: false,
83            },
84            {
85                title: 'Topology',
86                icon: 'add_circle_outline',
87                show: false,
88            },
89
90        ];
91
92        $scope.thread = {
93            networkName: 'OpenThreadDemo',
94            extPanId: '1111111122222222',
95            panId: '0x1234',
96            passphrase: 'j01Nme',
97            networkKey: '00112233445566778899aabbccddeeff',
98            channel: 15,
99            prefix: 'fd11:22::',
100            defaultRoute: true,
101        };
102
103        $scope.setting = {
104            prefix: 'fd11:22::',
105            defaultRoute: true,
106        };
107
108        $scope.headerTitle = 'Home';
109        $scope.status = [];
110
111        $scope.isLoading = false;
112
113        $scope.showScanAlert = function(ev) {
114            $mdDialog.show(
115                $mdDialog.alert()
116                .parent(angular.element(document.querySelector('#popupContainer')))
117                .clickOutsideToClose(true)
118                .title('Information')
119                .textContent('There is no available Thread network currently, please \
120                             wait a moment and retry it.')
121                .ariaLabel('Alert Dialog Demo')
122                .ok('Okay')
123            );
124        };
125        $scope.showPanels = function(index) {
126            $scope.headerTitle = $scope.menu[index].title;
127            for (var i = 0; i < 7; i++) {
128                $scope.menu[i].show = false;
129            }
130            $scope.menu[index].show = true;
131            if (index == 1) {
132                $scope.isLoading = true;
133                $http.get('available_network').then(function(response) {
134                    $scope.isLoading = false;
135                    if (response.data.error == 0) {
136                        $scope.networksInfo = response.data.result;
137                    } else {
138                        $scope.showScanAlert(event);
139                    }
140                });
141            }
142            if (index == 3) {
143                $http.get('get_properties').then(function(response) {
144                    console.log(response);
145                    if (response.data.error == 0) {
146                        var statusJson = response.data.result;
147                        $scope.status = [];
148                        for (var i = 0; i < Object.keys(statusJson).length; i++) {
149                            $scope.status.push({
150                                name: Object.keys(statusJson)[i],
151                                value: statusJson[Object.keys(statusJson)[i]],
152                                icon: 'res/img/icon-info.png',
153                            });
154                        }
155                    }
156                });
157            }
158            if (index == 6) {
159                $scope.dataInit();
160                $scope.showTopology();
161            }
162        };
163
164        $scope.showJoinDialog = function(ev, index, item) {
165            sharedProperties.setIndex(index);
166            sharedProperties.setNetworkInfo(item);
167            $scope.index = index;
168            $mdDialog.show({
169                controller: DialogController,
170                templateUrl: 'join.dialog.html',
171                parent: angular.element(document.body),
172                targetEvent: ev,
173                clickOutsideToClose: true,
174                fullscreen: $scope.customFullscreen,
175            });
176        };
177
178        function DialogController($scope, $mdDialog, $http, $interval, sharedProperties) {
179            var index = sharedProperties.getIndex();
180            $scope.isDisplay = false;
181            $scope.thread = {
182                networkKey: '00112233445566778899aabbccddeeff',
183                prefix: 'fd11:22::',
184                defaultRoute: true,
185            };
186
187            $scope.showAlert = function(ev, message) {
188                $mdDialog.show(
189                    $mdDialog.alert()
190                    .parent(angular.element(document.querySelector('#popupContainer')))
191                    .clickOutsideToClose(true)
192                    .title('Information')
193                    .textContent(message)
194                    .ariaLabel('Alert Dialog Demo')
195                    .ok('Okay')
196                    .targetEvent(ev)
197                );
198            };
199
200            $scope.showQRAlert = function(ev, message) {
201                $mdDialog.show(
202                    $mdDialog.alert()
203                    .parent(angular.element(document.querySelector('#popupContainer')))
204                    .clickOutsideToClose(true)
205                    .title('Information')
206                    .textContent(message)
207                    .ariaLabel('Alert Dialog Demo')
208                    .ok('Okay')
209                    .targetEvent(ev)
210                    .multiple(true)
211                );
212            };
213
214            $scope.showQRCode = function(ev, image) {
215              $mdDialog.show({
216                targetEvent: ev,
217                parent: angular.element(document.querySelector('#popupContainer')),
218                  template:
219                    '<md-dialog>' +
220                    '  <md-dialog-content>' +
221                    '  <h6 style="margin: 10px 10px 10px 10px; "><b>Open your OT Commissioner Android App to scan the Connect QR Code</b></h6>' +
222                    '  <div layout="row">' +
223                    '  <img ng-src="' + image + '" alt="qr code" style="display: block; margin-left: auto; margin-right: auto; width:40%"></img>' +
224                    '  </div>' +
225                    '  <md-dialog-actions>' +
226                    '    <md-button ng-click="closeDialog()" class="md-primary">' +
227                    '      Close' +
228                    '    </md-button>' +
229                    '  </md-dialog-actions>' +
230                    '</md-dialog>',
231                   controller: function DialogController($scope, $mdDialog) {
232                    $scope.closeDialog = function() {
233                    $mdDialog.hide();
234                    }
235                },
236                    multiple: true
237             });
238            };
239
240            $scope.join = function(valid) {
241                if (!valid)
242                {
243                    return;
244                }
245
246                if ($scope.thread.defaultRoute == null) {
247                    $scope.thread.defaultRoute = false;
248                };
249                $scope.isDisplay = true;
250                var data = {
251                    credentialType: $scope.thread.credentialType,
252                    networkKey: $scope.thread.networkKey,
253                    pskd: $scope.thread.pskd,
254                    prefix: $scope.thread.prefix,
255                    defaultRoute: $scope.thread.defaultRoute,
256                    index: index,
257                };
258                var httpRequest = $http({
259                    method: 'POST',
260                    url: 'join_network',
261                    data: data,
262                });
263
264                httpRequest.then(function successCallback(response) {
265                    $scope.res = response.data.result;
266                    if (response.data.result == 'successful') {
267                        $mdDialog.hide();
268                    }
269                    $scope.isDisplay = false;
270                    $scope.showAlert(event, "Join operation is " + response.data.result + ". " + response.data.message);
271                });
272            };
273
274            $scope.cancel = function() {
275                $mdDialog.cancel();
276            };
277
278            $scope.qrcode = function() {
279                $scope.isLoading = false;
280                $http.get('get_qrcode').then(function(response) {
281                    console.log(response);
282                    $scope.res = response.data.result;
283                    if (response.data.result == 'successful') {
284                        var image = "http://api.qrserver.com/v1/create-qr-code/?color=000000&amp;bgcolor=FFFFFF&amp;data=v%3D1%26%26eui%3D" + response.data.eui64 +"%26%26cc%3D" + $scope.thread.pskd +"&amp;qzone=1&amp;margin=0&amp;size=400x400&amp;ecc=L";
285                        $scope.showQRCode(event, image);
286                    } else {
287                        $scope.showQRAlert(event, "sorry, can not generate the QR code.");
288                    }
289                    $scope.isDisplay = true;
290
291                });
292            };
293        };
294
295
296        $scope.showConfirm = function(ev, valid) {
297            if (!valid)
298            {
299                return;
300            }
301
302            var confirm = $mdDialog.confirm()
303                .title('Are you sure you want to Form the Thread Network?')
304                .textContent('')
305                .targetEvent(ev)
306                .ok('Okay')
307                .cancel('Cancel');
308
309            $mdDialog.show(confirm).then(function() {
310                if ($scope.thread.defaultRoute == null) {
311                    $scope.thread.defaultRoute = false;
312                };
313                var data = {
314                    networkKey: $scope.thread.networkKey,
315                    prefix: $scope.thread.prefix,
316                    defaultRoute: $scope.thread.defaultRoute,
317                    extPanId: $scope.thread.extPanId,
318                    panId: $scope.thread.panId,
319                    passphrase: $scope.thread.passphrase,
320                    channel: $scope.thread.channel,
321                    networkName: $scope.thread.networkName,
322                };
323                $scope.isForming = true;
324                var httpRequest = $http({
325                    method: 'POST',
326                    url: 'form_network',
327                    data: data,
328                });
329
330                httpRequest.then(function successCallback(response) {
331                    $scope.res = response.data.result;
332                    if (response.data.result == 'successful') {
333                        $mdDialog.hide();
334                    }
335                    $scope.isForming = false;
336                    $scope.showAlert(event, 'FORM', response.data.result);
337                });
338            }, function() {
339                $mdDialog.cancel();
340            });
341        };
342
343        $scope.showAlert = function(ev, operation, result) {
344            $mdDialog.show(
345                $mdDialog.alert()
346                .parent(angular.element(document.querySelector('#popupContainer')))
347                .clickOutsideToClose(true)
348                .title('Information')
349                .textContent(operation + ' operation is ' + result)
350                .ariaLabel('Alert Dialog Demo')
351                .ok('Okay')
352                .targetEvent(ev)
353            );
354        };
355
356        $scope.showAddConfirm = function(ev) {
357            var confirm = $mdDialog.confirm()
358                .title('Are you sure you want to Add this On-Mesh Prefix?')
359                .textContent('')
360                .targetEvent(ev)
361                .ok('Okay')
362                .cancel('Cancel');
363
364            $mdDialog.show(confirm).then(function() {
365                if ($scope.setting.defaultRoute == null) {
366                    $scope.setting.defaultRoute = false;
367                };
368                var data = {
369                    prefix: $scope.setting.prefix,
370                    defaultRoute: $scope.setting.defaultRoute,
371                };
372                var httpRequest = $http({
373                    method: 'POST',
374                    url: 'add_prefix',
375                    data: data,
376                });
377
378                httpRequest.then(function successCallback(response) {
379                    $scope.showAlert(event, 'Add', response.data.result);
380                });
381            }, function() {
382                $mdDialog.cancel();
383            });
384        };
385
386        $scope.showDeleteConfirm = function(ev) {
387            var confirm = $mdDialog.confirm()
388                .title('Are you sure you want to Delete this On-Mesh Prefix?')
389                .textContent('')
390                .targetEvent(ev)
391                .ok('Okay')
392                .cancel('Cancel');
393
394            $mdDialog.show(confirm).then(function() {
395                var data = {
396                    prefix: $scope.setting.prefix,
397                };
398                var httpRequest = $http({
399                    method: 'POST',
400                    url: 'delete_prefix',
401                    data: data,
402                });
403
404                httpRequest.then(function successCallback(response) {
405                    $scope.showAlert(event, 'Delete', response.data.result);
406                });
407            }, function() {
408                $mdDialog.cancel();
409            });
410        };
411
412        $scope.startCommission = function(ev) {
413            var data = {
414                pskd: $scope.commission.pskd,
415                passphrase: $scope.commission.passphrase,
416            };
417            var httpRequest = $http({
418                method: 'POST',
419                url: 'commission',
420                data: data,
421            });
422
423            ev.target.disabled = true;
424
425            httpRequest.then(function successCallback(response) {
426                if (response.data.error == 0) {
427                    $scope.showAlert(event, 'Commission', 'success');
428                } else {
429                    $scope.showAlert(event, 'Commission', 'failed');
430                }
431                ev.target.disabled = false;
432            });
433        };
434
435        $scope.restServerPort = '8081';
436        $scope.ipAddr = window.location.hostname + ':' + $scope.restServerPort;
437
438        // Basic information line
439        $scope.basicInfo = {
440            'NetworkName' : 'Unknown',
441            'LeaderData'  :{'LeaderRouterId' : 'Unknown'}
442        }
443        // Num of router calculated by diagnostic
444        $scope.NumOfRouter = 'Unknown';
445
446        // Diagnostic information for detailed display
447        $scope.nodeDetailInfo = 'Unknown';
448        // For response of Diagnostic
449        $scope.networksDiagInfo = '';
450        $scope.graphisReady = false;
451        $scope.detailList = {
452            'ExtAddress': { 'title': false, 'content': true },
453            'Rloc16': { 'title': false, 'content': true },
454            'Mode': { 'title': false, 'content': false },
455            'Connectivity': { 'title': false, 'content': false },
456            'Route': { 'title': false, 'content': false },
457            'LeaderData': { 'title': false, 'content': false },
458            'NetworkData': { 'title': false, 'content': true },
459            'IP6Address List': { 'title': false, 'content': true },
460            'MACCounters': { 'title': false, 'content': false },
461            'ChildTable': { 'title': false, 'content': false },
462            'ChannelPages': { 'title': false, 'content': false }
463        };
464        $scope.graphInfo = {
465            'nodes': [],
466            'links': []
467        }
468
469        $scope.dataInit = function() {
470
471            $http.get('http://' + $scope.ipAddr + '/node').then(function(response) {
472
473                $scope.basicInfo = response.data;
474                console.log(response.data);
475                $scope.basicInfo.Rloc16 = $scope.intToHexString($scope.basicInfo.Rloc16,4);
476                $scope.basicInfo.LeaderData.LeaderRouterId = '0x' + $scope.intToHexString($scope.basicInfo.LeaderData.LeaderRouterId,2);
477            });
478        }
479        $scope.isObject = function(obj) {
480            return obj.constructor === Object;
481        }
482        $scope.isArray = function(arr) {
483            return !!arr && arr.constructor === Array;
484        }
485
486        $scope.clickList = function(key) {
487            $scope.detailList[key]['content'] = !$scope.detailList[key]['content']
488        }
489
490        $scope.intToHexString = function(num, len){
491            var value;
492            value  = num.toString(16);
493
494            while( value.length < len ){
495                value = '0' + value;
496            }
497            return value;
498        }
499        $scope.showTopology = function() {
500            var nodeMap = {}
501            var count, src, dist, rloc, child, rlocOfParent, rlocOfChild, diagOfNode, linkNode, childInfo;
502
503            $scope.graphisReady = false;
504            $scope.graphInfo = {
505                'nodes': [],
506                'links': []
507            };
508            $http.get('http://' + $scope.ipAddr + '/diagnostics').then(function(response) {
509
510
511                $scope.networksDiagInfo = response.data;
512                for (diagOfNode of $scope.networksDiagInfo){
513
514                    diagOfNode['RouteId'] = '0x' + $scope.intToHexString(diagOfNode['Rloc16'] >> 10,2);
515
516                    diagOfNode['Rloc16'] = '0x' + $scope.intToHexString(diagOfNode['Rloc16'],4);
517
518                    diagOfNode['LeaderData']['LeaderRouterId'] = '0x' + $scope.intToHexString(diagOfNode['LeaderData']['LeaderRouterId'],2);
519                    for (linkNode of diagOfNode['Route']['RouteData']){
520                        linkNode['RouteId'] = '0x' + $scope.intToHexString(linkNode['RouteId'],2);
521                    }
522                }
523
524                count = 0;
525
526                for (diagOfNode of $scope.networksDiagInfo) {
527                    if ('ChildTable' in diagOfNode) {
528
529                        rloc = parseInt(diagOfNode['Rloc16'],16).toString(16);
530                        nodeMap[rloc] = count;
531
532                        if ( diagOfNode['RouteId'] == diagOfNode['LeaderData']['LeaderRouterId']) {
533                            diagOfNode['Role'] = 'Leader';
534                        } else {
535                            diagOfNode['Role'] = 'Router';
536                        }
537
538                        $scope.graphInfo.nodes.push(diagOfNode);
539
540                        if (diagOfNode['Rloc16'] === $scope.basicInfo.rloc16) {
541                            $scope.nodeDetailInfo = diagOfNode
542                        }
543                        count = count + 1;
544                    }
545                }
546                // Num of Router is based on the diagnostic information
547                $scope.NumOfRouter = count;
548
549                // Index for a second loop
550                src = 0;
551                // Construct links
552                for (diagOfNode of $scope.networksDiagInfo) {
553                    if ('ChildTable' in diagOfNode) {
554                        // Link bewtwen routers
555                        for (linkNode of diagOfNode['Route']['RouteData']) {
556                            rloc = ( parseInt(linkNode['RouteId'],16) << 10).toString(16);
557                            if (rloc in nodeMap) {
558                                dist = nodeMap[rloc];
559                                if (src < dist) {
560                                    $scope.graphInfo.links.push({
561                                        'source': src,
562                                        'target': dist,
563                                        'weight': 1,
564                                        'type': 0,
565                                        'linkInfo': {
566                                            'inQuality': linkNode['LinkQualityIn'],
567                                            'outQuality': linkNode['LinkQualityOut']
568                                        }
569                                    });
570                                }
571                            }
572                        }
573
574                        // Link between router and child
575                        for (childInfo of diagOfNode['ChildTable']) {
576                            child = {};
577                            rlocOfParent = parseInt(diagOfNode['Rloc16'],16).toString(16);
578                            rlocOfChild = (parseInt(diagOfNode['Rloc16'],16) + childInfo['ChildId']).toString(16);
579
580                            src = nodeMap[rlocOfParent];
581
582                            child['Rloc16'] = '0x' + rlocOfChild;
583                            child['RouteId'] = diagOfNode['RouteId'];
584                            nodeMap[rlocOfChild] = count;
585                            child['Role'] = 'Child';
586                            $scope.graphInfo.nodes.push(child);
587                            $scope.graphInfo.links.push({
588                                'source': src,
589                                'target': count,
590                                'weight': 1,
591                                'type': 1,
592                                'linkInfo': {
593                                    'Timeout': childInfo['Timeout'],
594                                    'Mode': childInfo['Mode']
595                                }
596
597                            });
598
599                            count = count + 1;
600                        }
601                    }
602                    src = src + 1;
603                }
604
605                $scope.drawGraph();
606            })
607        }
608
609
610        $scope.updateDetailLabel = function() {
611            for (var detailInfoKey in $scope.detailList) {
612                $scope.detailList[detailInfoKey]['title'] = false;
613            }
614            for (var diagInfoKey in $scope.nodeDetailInfo) {
615                if (diagInfoKey in $scope.detailList) {
616                    $scope.detailList[diagInfoKey]['title'] = true;
617                }
618
619            }
620        }
621
622
623        $scope.drawGraph = function() {
624            var json, svg, tooltip, force;
625            var scale, len;
626
627            document.getElementById('topograph').innerHTML = '';
628            scale = $scope.graphInfo.nodes.length;
629            len = 125 * Math.sqrt(scale);
630
631            // Topology graph
632            svg = d3.select('.d3graph').append('svg')
633                .attr('preserveAspectRatio', 'xMidYMid meet')
634                .attr('viewBox', '0, 0, ' + len.toString(10) + ', ' + (len / (3 / 2)).toString(10));
635
636            // Legend
637            svg.append('circle')
638                .attr('cx',len-20)
639                .attr('cy',10).attr('r', 3)
640                .style('fill', "#7e77f8")
641                .style('stroke', '#484e46')
642                .style('stroke-width', '0.4px');
643
644            svg.append('circle')
645                .attr("cx",len-20)
646                .attr('cy',20)
647                .attr('r', 3)
648                .style('fill', '#03e2dd')
649                .style('stroke', '#484e46')
650                .style('stroke-width', '0.4px');
651
652            svg.append('circle')
653                .attr('cx',len-20)
654                .attr('cy',30)
655                .attr('r', 3)
656                .style('fill', '#aad4b0')
657                .style('stroke', '#484e46')
658                .style('stroke-width', '0.4px')
659                .style('stroke-dasharray','2 1');
660
661            svg.append('circle')
662                .attr('cx',len-50)
663                .attr('cy',10).attr('r', 3)
664                .style('fill', '#ffffff')
665                .style('stroke', '#f39191')
666                .style('stroke-width', '0.4px');
667
668            svg.append('text')
669                .attr('x', len-15)
670                .attr('y', 10)
671                .text('Leader')
672                .style('font-size', '4px')
673                .attr('alignment-baseline','middle');
674
675            svg.append('text')
676                .attr('x', len-15)
677                .attr('y',20 )
678                .text('Router')
679                .style('font-size', '4px')
680                .attr('alignment-baseline','middle');
681
682            svg.append('text')
683                .attr('x', len-15)
684                .attr('y',30 )
685                .text('Child')
686                .style('font-size', '4px')
687                .attr('alignment-baseline','middle');
688
689            svg.append('text')
690                .attr('x', len-45)
691                .attr('y',10 )
692                .text('Selected')
693                .style('font-size', '4px')
694                .attr('alignment-baseline','middle');
695
696            // Tooltip style  for each node
697            tooltip = d3.select('body')
698                .append('div')
699                .attr('class', 'tooltip')
700                .style('position', 'absolute')
701                .style('z-index', '10')
702                .style('visibility', 'hidden')
703                .text('a simple tooltip');
704
705            force = d3.layout.force()
706                .distance(40)
707                .size([len, len / (3 / 2)]);
708
709
710            json = $scope.graphInfo;
711
712            force
713                .nodes(json.nodes)
714                .links(json.links)
715                .start();
716
717
718            var link = svg.selectAll('.link')
719                .data(json.links)
720                .enter().append('line')
721                .attr('class', 'link')
722                .style('stroke', '#908484')
723                // Dash line for link between child and parent
724                .style('stroke-dasharray', function(item) {
725                    if ('Timeout' in item.linkInfo) return '4 4';
726                    else return '0 0'
727                })
728                // Line width representing link quality
729                .style('stroke-width', function(item) {
730                    if ('inQuality' in item.linkInfo)
731                        return Math.sqrt(item.linkInfo.inQuality/2);
732                    else return Math.sqrt(0.5)
733                })
734                // Effect of mouseover on a line
735                .on('mouseover', function(item) {
736                    return tooltip.style('visibility', 'visible')
737                        .text(item.linkInfo);
738                })
739                .on('mousemove', function() {
740                    return tooltip.style('top', (d3.event.pageY - 10) + 'px')
741                        .style('left', (d3.event.pageX + 10) + 'px');
742                })
743                .on('mouseout', function() {
744                    return tooltip.style('visibility', 'hidden');
745                });
746
747
748            var node = svg.selectAll('.node')
749                .data(json.nodes)
750                .enter().append('g')
751                .attr('class', function(item) {
752                        return item.Role;
753                })
754                .call(force.drag)
755                // Tooltip effect of mouseover on a node
756                .on('mouseover', function(item) {
757                    return tooltip.style('visibility', 'visible')
758                                  .text(item.Rloc16 );
759                })
760                .on('mousemove', function() {
761                    return tooltip.style('top', (d3.event.pageY - 10) + 'px')
762                                  .style('left', (d3.event.pageX + 10) + 'px');
763                })
764                .on('mouseout', function() {
765                    return tooltip.style('visibility', 'hidden');
766                });
767
768            d3.selectAll('.Child')
769                .append('circle')
770                .attr('r', '6')
771                .attr('fill', '#aad4b0')
772                .style('stroke', '#484e46')
773                .style('stroke-dasharray','2 1')
774                .style('stroke-width', '0.5px')
775                .attr('class', function(item) {
776                    return item.Rloc16;
777                })
778                .on('mouseover', function(item) {
779                    return tooltip.style('visibility', 'visible')
780                                  .text(item.Rloc16 );
781                })
782                .on('mousemove', function() {
783                    return tooltip.style('top', (d3.event.pageY - 10) + 'px')
784                                  .style('left', (d3.event.pageX + 10) + 'px');
785                })
786                .on('mouseout', function() {
787                    return tooltip.style('visibility', 'hidden');
788                });
789
790
791            d3.selectAll('.Leader')
792                .append('circle')
793                .attr('r', '8')
794                .attr('fill', '#7e77f8')
795                .style('stroke', '#484e46')
796                .style('stroke-width', '1px')
797                .attr('class', function(item) {
798                    return 'Stroke';
799                })
800                // Effect that node will become bigger when mouseover
801                .on('mouseover', function(item) {
802                    d3.select(this)
803                        .transition()
804                        .attr('r','9');
805                    return tooltip.style('visibility', 'visible')
806                                  .text(item.Rloc16);
807                })
808                .on('mousemove', function() {
809                    return tooltip.style('top', (d3.event.pageY - 10) + 'px')
810                                  .style('left', (d3.event.pageX + 10) + 'px');
811                })
812                .on('mouseout', function() {
813                    d3.select(this).transition().attr('r','8');
814                        return tooltip.style('visibility', 'hidden');
815                })
816                // Effect that node will have a yellow edge when clicked
817                .on('click', function(item) {
818                    d3.selectAll('.Stroke')
819                        .style('stroke', '#484e46')
820                        .style('stroke-width', '1px');
821                    d3.select(this)
822                        .style('stroke', '#f39191')
823                        .style('stroke-width', '1px');
824                    $scope.$apply(function() {
825                        $scope.nodeDetailInfo = item;
826                        $scope.updateDetailLabel();
827                    });
828                });
829            d3.selectAll('.Router')
830                .append('circle')
831                .attr('r', '8')
832                .style('stroke', '#484e46')
833                .style('stroke-width', '1px')
834                .attr('fill', '#03e2dd')
835                .attr('class','Stroke')
836                .on('mouseover', function(item) {
837                    d3.select(this)
838                        .transition()
839                        .attr('r','8');
840                    return tooltip.style('visibility', 'visible')
841                                  .text(item.Rloc16);
842                })
843                .on('mousemove', function() {
844                    return tooltip.style('top', (d3.event.pageY - 10) + 'px')
845                                  .style('left', (d3.event.pageX + 10) + 'px');
846                })
847                .on('mouseout', function() {
848                    d3.select(this)
849                        .transition()
850                        .attr('r','7');
851                    return tooltip.style('visibility', 'hidden');
852                })
853                // The same effect as Leader
854                .on('click', function(item) {
855                    d3.selectAll('.Stroke')
856                        .style('stroke', '#484e46')
857                        .style('stroke-width', '1px');
858                    d3.select(this)
859                        .style('stroke', '#f39191')
860                        .style('stroke-width', '1px');
861                    $scope.$apply(function() {
862                        $scope.nodeDetailInfo = item;
863                        $scope.updateDetailLabel();
864                    });
865                });
866
867            force.on('tick', function() {
868                link.attr('x1', function(item) { return item.source.x; })
869                    .attr('y1', function(item) { return item.source.y; })
870                    .attr('x2', function(item) { return item.target.x; })
871                    .attr('y2', function(item) { return item.target.y; });
872                node.attr('transform', function(item) {
873                    return 'translate(' + item.x + ',' + item.y + ')';
874                });
875            });
876
877            $scope.updateDetailLabel();
878            $scope.graphisReady = true;
879
880        }
881    };
882})();
883