raphael.js+servlet仿http://map.norsecorp.com/做态势感知

来源:互联网 发布:编程语言实现模式 源码 编辑:程序博客网 时间:2024/05/20 03:40

1.echarts做动态网络攻击缺点:

   (1)太死板,灵活性不够,动态攻击时效果一致性不能满足

    (2)高度化集成,攻击线条和攻击时效果单一,不能使用自定义攻击图像和动画效果

2.raphael能解决这些:基于svg,可以自定义动画图像和攻击效果,可以添加回调事件,可以逐步播放动画(有点像flash帧播放)


3,代码部分:

(1)html部分:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Map with some updates on links performed</title>
    <style type="text/css">
.div-a0{
border: 5px solid;
position: absolute;
height: 7%;
width: 17%;
z-index: 1000;
opacity: 0.65;
left:-4%;
top:56.5%;
color: rgba(0,0,0,0.1);
background-color: black;
    }

.div-a{
border: 5px solid;
position: absolute;
height: 35%;
width: 17%;
z-index: 1000;
opacity: 0.65;
left:-2.5%;
top:62%;
color: rgba(0,0,0,0);
background-color:rgba(0,0,0,0);
    }


.div-c0{
border: 5px solid;
position: absolute;
height: 6.8%;
width: 23.52%;
z-index: 1001;
opacity: 0.65;
left:16%;
top:56.5%;
color: rgba(0,0,0,0.4);
background-color: black;
    }
.div-c{
border: 1px solid;
position: absolute;
height: 35%;
width: 24.005%;
z-index: 100;
opacity: 0.65;
left:16%;
top:62%;
color: rgba(0,0,0,0.00001);
background-color: black;
    }

.div-d0{
border: 5px solid;
position: absolute;
height: 7%;
width: 13%;
z-index: 1000;
opacity: 0.65;
left:42%;
top:56.5%;
color: rgba(0,0,0,0.1);
background-color: black;
    }

.div-d{
border: 1px solid;
position: absolute;
height: 35%;
width: 16%;
z-index: 1000;
opacity: 0.65;
left:39%;
top:62%;
color: rgba(0,0,0,0);
background-color: rgba(0,0,0,0);
    }

.div-b0{
border: 5px solid;
position: absolute;
height: 6.8%;
width: 40.6%;
z-index: 1001;
opacity: 0.85;
left:58.5%;
top:56.5%;
color: rgba(0,0,0,1.4);
background-color: black;
    }
.div-b{
border: 1px solid;
position: absolute;
height: 35%;
width: 41.2%;
z-index: 1000;
opacity: 0.65;
left:58.5%;
top:62%;
color: rgba(0,0,0,0.1);
background-color: black;
    }



        body {
            //color: #5d5d5d;
            font-family: Helvetica, Arial, sans-serif;
        }


        h1 {
            font-size: 30px;
            margin: auto;
            margin-top: 50px;
        }


        .container {
            max-width: 1800px;
            margin: auto;
//background:#002F47;
        }


        /* Specific mapael css class are below
         * 'mapael' class is added by plugin
        */


        .mapael .map {
            position: absolute;
 background:#002F47;
        }


        .mapael .mapTooltip {
            position: fixed;
            background-color: #fff;
            moz-opacity: 0.70;
            opacity: 0.70;
            filter: alpha(opacity=70);
            border-radius: 10px;
            padding: 10px;
            z-index: 1000;
            max-width: 200px;
            display: none;
            color: red;
        }


        /* For all zoom buttons */
        .mapael .zoomButton {
            background-color: #fff;
            border: 1px solid #ccc;
            color: #000;
            width: 15px;
            height: 15px;
            line-height: 15px;
            text-align: center;
            border-radius: 3px;
            cursor: pointer;
            position: absolute;
            top: 0;
            font-weight: bold;
            left: 10px;


            -webkit-user-select: none;
            -khtml-user-select : none;
            -moz-user-select: none;
            -o-user-select : none;
            user-select: none;
        }


        /* Reset Zoom button first */
        .mapael .zoomReset {
            top: 10px;
        }


        /* Then Zoom In button */
        .mapael .zoomIn {
            top: 30px;
        }


        /* Then Zoom Out button */
        .mapael .zoomOut {
            top: 50px;
        }

.block .header, .block .content, .block .footer, .block .toolbar, .block .header.header-default, .block .content.content-default, .block .toolbar.toolbar-default, .block .footer.footer-defaut {
background-color: rgba(0,0,0,0.1);
}




.rt-attack-lista .rt-attack-lista .rt-attack-list li div{
font-size:x-large;
color: #FD0202;
text-shadow: 0 5px 9px #c4b59d, 0px -2px 1px #fff;
font-weight: bold;
letter-spacing: -4px;
text-align: center;
}


/* 攻击排名 */
/* -------------------------滚动显示start-----------------------------------  */
.topRec_Lista dl,.maqueea{ width:100%; overflow:hidden; margin:0 auto; color:#7C7C7C;}
.topRec_Lista dd{ float:left; text-align:center;color:red;}
.topRec_Lista dl dd:nth-child(1){ width:10%; height:20px; line-height:20px; }
.topRec_Lista dl dd:nth-child(2){ width:40%; height:20px; line-height:20px; }




/* .maqueea{ height:90%;}  */
.topRec_Lista ul{ width:100%; height:150px;}
.topRec_Lista li{ width:100%; height:25px; line-height:25px; text-align:center; font-size:12px;color:#2e2e2f;}


.topRec_Lista li div{ float:left;color: red}
.topRec_Lista li div:nth-child(1){ width:10%;}
.topRec_Lista li div:nth-child(2){ width:80%;}


/* -------------------------滚动显示end-----------------------------------  */


/* 攻击类型 */
/* -------------------------滚动显示start-----------------------------------  */
.topRec_Listc dl,.maqueea{ width:100%; overflow:hidden; margin:0 auto; color:#7C7C7C;}
//.topRec_Listc dd{ float:left; text-align:center; border-bottom:1px solid #1B96EE; color:#f29b10;}
.topRec_Listc dd{ float:left; text-align:center;color:red;}
.topRec_Listc dl dd:nth-child(1){ width:10%; height:20px; line-height:20px; }
.topRec_Listc dl dd:nth-child(2){ width:20%; height:20px; line-height:20px; }
.topRec_Listc dl dd:nth-child(3){ width:30%; height:20px; line-height:20px; }




/* .maqueea{ height:90%;}  */
.topRec_Listc ul{ width:100%; height:150px;}
.topRec_Listc li{ width:100%; height:25px; line-height:25px; text-align:center; font-size:12px;color:#2e2e2f;}


.topRec_Listc li div{ float:left;color: red}
.topRec_Listc li div:nth-child(1){ width:10%;}
.topRec_Listc li div:nth-child(2){ width:42%;}
.topRec_Listc li div:nth-child(3){ width:30%;}


/* -------------------------滚动显示end-----------------------------------  */


/* 滚动显示攻击信息 */
/* -------------------------滚动显示start-----------------------------------  */
.topRec_List dl,.maquee{ width:100%; overflow:hidden; margin:0 auto; color:#7C7C7C;}
//.topRec_List dd{ float:left; text-align:center; border-bottom:1px solid #1B96EE; color:#f29b10;}
.topRec_List dd{ float:left; text-align:center;color:red;}
.topRec_List dl dd:nth-child(1){ width:17%; height:20px; line-height:20px; }
.topRec_List dl dd:nth-child(2){ width:10.5%; height:20px; line-height:20px; }
.topRec_List dl dd:nth-child(3){ width:16.5%; height:20px; line-height:20px; }
.topRec_List dl dd:nth-child(4){ width:8%; height:20px; line-height:20px; }
.topRec_List dl dd:nth-child(5){ width:14%; height:20px; line-height:20px; }






/* .maquee{ height:90%;}  */
.topRec_List ul{ width:100%; height:150px;}
.topRec_List li{ width:100%; height:25px; line-height:25px; text-align:center; font-size:12px;color:#2e2e2f;}


.topRec_List li div{ float:left;color: red}
.topRec_List li div:nth-child(1){ width:19%;}
.topRec_List li div:nth-child(2){ width:22%;}
.topRec_List li div:nth-child(3){ width:17%;}
.topRec_List li div:nth-child(4){ width:18%;}
.topRec_List li div:nth-child(5){ width:17%;}






/* -------------------------滚动显示end-----------------------------------  */


/* 攻击排名 */
/* -------------------------滚动显示start-----------------------------------  */
li{ list-style:none }
.topRec_Listd dl,.maqueed{ width:100%; overflow:hidden; margin:0 auto; color:#7C7C7C;}
//.topRec_Listd dd{ float:left; text-align:center; border-bottom:1px solid #1B96EE; color:#f29b10;}
.topRec_Listd dd{ float:left; text-align:center;color:red;}
.topRec_Listd dl dd:nth-child(1){ width:20%; height:20px; line-height:20px; }
.topRec_Listd dl dd:nth-child(2){ width:40%; height:20px; line-height:20px; }




/* .maqueed{ height:90%;}  */
.topRec_Listd ul{ width:100%; height:150px;}
.topRec_Listd li{ width:100%; height:25px; line-height:25px; text-align:center; font-size:12px;color:#2e2e2f;}


.topRec_Listd li div{ float:left;color: red}
.topRec_Listd li div:nth-child(1){ width:20%;}
.topRec_Listd li div:nth-child(2){ width:80%;}


/* -------------------------滚动显示end-----------------------------------  */


.contents{
position: relative;
left:-12%;
top:60%;
color: rgba(0,0,0,0.1);
}
.mapael .map {
            position: absolute;
 background:#000000;
 color:red;
        }


        .mapael .mapTooltip {
            position: fixed;
            background: yellow;
            moz-opacity: 0.70;
            opacity: 0.70;
            filter: alpha(opacity=70);
            border-radius: 10px;
            padding: 10px;
            z-index: 1000;
            max-width: 200px;
            display: none;
            color: red;
        }


        /* For all zoom buttons */
        .mapael .zoomButton {
            background-color: #fff;
            border: 1px solid #ccc;
            color: #000;
            width: 15px;
            height: 15px;
            line-height: 15px;
            text-align: center;
            border-radius: 3px;
            cursor: pointer;
            position: absolute;
            top: 0;
            font-weight: bold;
            left: 10px;


            -webkit-user-select: none;
            -khtml-user-select : none;
            -moz-user-select: none;
            -o-user-select : none;
            user-select: none;
        }


        /* Reset Zoom button first */
        .mapael .zoomReset {
            top: 10px;
        }


        /* Then Zoom In button */
        .mapael .zoomIn {
            top: 30px;
        }


        /* Then Zoom Out button */
        .mapael .zoomOut {
            top: 50px;
        }
.texts{width:100%; height:105px; text-align:right; font-size:22px;color:#2e2e2f;}
    </style>


   <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js" charset="utf-8"></script>  -->
<script src="js/jquery.js" charset="utf-8"></script>
    <!--<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"
            charset="utf-8"></script>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js" charset="utf-8"></script> -->
<script src="js/raphael.min.js" charset="utf-8"></script>


    <script src="js/jquery.mapael.js" charset="utf-8"></script>
 
    <script src="js/maps/france_departments.js" charset="utf-8"></script>
    <script src="js/maps/world_countries.js" charset="utf-8"></script>
    <script src="js/maps/china_departments.js" charset="utf-8"></script>
<script src="js/guides.js" charset="utf-8"></script>
    <script type="text/javascript">
        $(function () {
        //var city='lyon';
        var geoCoordMap = {
        'lyon': {latitude: 47.758888888889,
                        longitude: 4.1413888888889,
                    },
                    'rennes': {
                        latitude: 48.114166666667,
                        longitude: 3.6808333333333,
                    },
'boertu': {
                        latitude: 49.114166666667,
                        longitude: 3.6808333333333,
                    },
'boertu1': {
                        latitude: 49.114166666667,
                        longitude: 4.6808333333333,
                    },
'boertu2': {
                        latitude: 48.114166666667,
                        longitude: 2.6808333333333,
                    },
'包头': {
                        latitude: 47.3467,
                        longitude: 3.4899,
                    }, 
                    "city1": {latitude:48.4229, longitude:4.478},
                    "city2": {latitude:48.4183, longitude:4.5615},
                    "city3": {latitude:48.3118, longitude:3.2936},
                    "city4": {latitude:48.5415, longitude:4.4242},
                    "city5": {latitude:49.5642, longitude:4.8854}
        };
        var linksJson={};
        
        //alert(geoCoordMap[city]);
        $(".mapcontainer").mapael({
        map: {
                    name: "china_departments"
                    , zoom: {
                        enabled: true
                    }
                    , defaultArea: {
                        attrs: {
                            fill: "#000000", 
stroke: "#299090",
"stroke-width":1.35
                        }
                        , text: {
                            attrs: {
                                fill: "#505444"
                            }
                            , attrsHover: {
                                fill: "#000"
                            }
                        }
                    }
                },


                plots:geoCoordMap,
                links: {}
            });


        setInterval(function(){
       
        var orgindata;
                var attackdata;
                var attacktypedata;
                var liveattackdata;
                var orginHtml = '';
                var attackHtml = '';
                var attacktypeHtml='';
                var liveattackHtml='';
        $.ajax({
                type:"post",
                async:true,
                url:'TestServlet1',
                data:{},
                dataType:"json",
                success:function(data){
                orgindata=eval(data)[0]['orginlist'];
                attackdata=eval(data)[0]['attackedlist'];
                attacktypedata=eval(data)[0]['attacktypelist'];
                liveattackdata=eval(data)[0]['liveattacklist'];
                $.each(orgindata,function(i,item){
                orginHtml += '<li><div>'+item.count+'</div><div>'+item.city+'</div></li>';
                });
                $("#rt-attack-lista").html(orginHtml);
               
                $.each(attackdata,function(i,item){
                attackHtml += '<li><div>'+item.count+'</div><div>'+item.city+'</div></li>';
                });
                $("#rt-attack-listd").html(attackHtml);
               
                $.each(attacktypedata,function(i,item){
                attacktypeHtml += '<li><div>'+item.count+'</div><div>'+item.port+'</div><div>'+item.type+'</div</li>';
                });
                $("#rt-attack-listc").html(attacktypeHtml);
               
               
                $.each(liveattackdata,function(i,item){
                var linkslist=[];
                linkslist.push({
                                factor: -0.0
                                , between: [item.fromAddr, item.toAddr]
                                , attrs: {
                                    "stroke-width": 1,
        opacity: .0, 
        "stroke": item.color
                                }
                            }); 
                linksJson[item.attacker]=linkslist[0];
                if(item.type=="smtp"){
                liveattackHtml += '<li><div>'+item.time+'</div><div>'+item.attacker+'</div><div>'+item.orgin+'</div><div style="color:green;">'+item.type+'</div><div>'+item.port+'</div></li>';
    }else if(item.type=="ftp"){
                liveattackHtml += '<li><div>'+item.time+'</div><div>'+item.attacker+'</div><div>'+item.orgin+'</div><div style="color:red;">'+item.type+'</div><div>'+item.port+'</div></li>';
    }else{
                liveattackHtml += '<li><div>'+item.time+'</div><div>'+item.attacker+'</div><div>'+item.orgin+'</div><div style="color:blue;">'+item.type+'</div><div>'+item.port+'</div></li>';
    }
                });
                var linesattr=JSON.stringify(linksJson);
                lineobjs=JSON.parse(linesattr);
                $("#rt-attack-list").html(liveattackHtml);
                setInterval(function(){
                $(".maquee").find("ul").animate({
            marginTop : "-25px"
            }, 500, function() {
            $(this).css({
            marginTop : "0px"
            }).find("li:first").appendTo(this);
            });
            }, 100);
               
                },
                error:function(errorMsg){
                alert("获取数据失败!")
                }
                })
    $(".maquee").find("ul").animate({
marginTop : "-25px"
}, 500, function() {
$(this).css({
marginTop : "0px"
}).find("li:first").appendTo(this);
});
                var opt = {
                    mapOptions: {
                        'areas': {},
                        'plots': {},
                        'links': {
                            'parislyon': {
                                attrs: {
                                    'stroke-width': 8
                                    , 'stroke': 'red'
                                }
                            }
                        },


                    }, 
                    //var arr1="{'boertu-rennes': {factor: -0.0, between: ['boertu', 'rennes'], attrs: {'stroke-width': 1,opacity: .0,'stroke': '#666' }}";
                    animDuration: 1000,
                    'deleteLinkKeys': ['parisrennes'],
                    'newLinks':linksJson/*{
                        
                    'boertu-rennes': {
                            factor: -0.0
                            , between: ['boertu', 'rennes']
                            , attrs: {
                                "stroke-width": 1,
    opacity: .0, 
    "stroke": "red"
                            }
                        },
                        'boertu-boertu': {
                            factor: -0.0
                            , between: ['boertu1', 'boertu2']
                            , attrs: {
                                "stroke-width": 1,
    opacity: .0, 
    "stroke": "blue"
                            }
                        },
                        '包头-boertu2': {
                            factor: -0.0
                            , between: ['包头', 'boertu2']
                            , attrs: {
                                "stroke-width": 1,
    opacity: .0, 
    "stroke": "green"
                            }
                        }

                    } */
                };


                $(".mapcontainer").trigger('update', [opt]);
            //});
        }, 1000);
        });
    </script>


</head>


<body>
<div class="container">


    <div class="mapcontainer">
        <input type="button" value="Update elements" id="refresh"/>


        <div class="map" style="text-align:right;height:735px;width:1580px;text-align:center;">
            
        </div>
    </div>


</div>
 <div class="div-a0"><h4><span class="texts">&nbsp;  &nbsp; &nbsp; &nbsp; &nbsp;ATTACK ORIGINS</span></h4></div>
<div class="div-a">
<ul>
<li id="attack1" class="block flex-content" left="0.2%" data-row="4" data-col="1" data-sizex="2" data-sizey="2" style="overflow: hidden;">
<div class="contents" style="overflow: hidden;">
<div id="rt-attacka">
<div class="topRec_Lista">
<dl>
        <dd>#</dd>
        <dd>origin city</dd>  
       </dl>
<div class="maqueea">
<ul id="rt-attack-lista">
<!-- <li>
       <div>10</div>
       <div>北京</div>       
   </li>
   <li>
       <div>8</div>
       <div>上海</div>   
   </li>
   <li>
       <div>5</div>
       <div>深圳</div>
   </li>
   <li>
       <div>3</div>
       <div>武汉</div>
   </li>
   <li>
       <div>3</div>
       <div>西安</div>
   </li>
   <li>
       <div>3</div>
       <div>杭州</div>
   </li>
   <li>
       <div>1</div>
       <div>成都</div>
   </li> -->   
</ul>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
<div class="div-c0"><h4><span class="texts">ATTACK TYPES</span></h4></div>
<div class="div-c">
<ul>
<li id="attack1" class="block flex-content" left="0.2%" data-row="4" data-col="1" data-sizex="2" data-sizey="2" style="overflow: hidden;">
<div class="contents" style="overflow: hidden;">
<div id="rt-attackc">
<div class="topRec_Listc">
<dl>
        <dd>#</dd>
<dd>port</dd>
        <dd>攻击类型</dd>  
       </dl>
<div class="maqueea">
<ul id="rt-attack-listc">
<li>
       <div>10</div>
       <div>445</div>
<div>smtp</div>       
   </li>
   <li>
       <div>8</div>
<div>88</div>
       <div>telnet</div>   
   </li>
   <li>
       <div>5</div>
<div>512</div>
       <div>http-alt</div>
   </li>
   <li>
       <div>3</div>
       <div>3301</div>
<div>netis-router</div>
   </li>
   <li>
       <div>3</div>
       <div>3380</div>
<div>rfb</div>
   </li>
   <li>
       <div>3</div>
       <div>8808</div>
<div>ntp</div>
   </li>
   <li>
       <div>1</div>
       <div>9090</div>
<div>dnf</div>
   </li>   
</ul>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
<div class="div-b0"><h4><span class="texts">LIVE ATTACK</span></h4></div>
<div class="div-b">
<ul>
<!-- 攻击地图 -->
<li id="attack" class="block flex-content" left="0.2%" data-row="4" data-col="1" data-sizex="2" data-sizey="2" style="overflow: hidden;">
<div class="contents" style="overflow: hidden;">
<div id="rt-attack">
<div class="topRec_List">
<dl>
        <dd>TIME</dd>
        <dd>ATTACKER</dd>
        <dd>ATTACKER IP</dd>
        <dd>TYPE</dd>
<dd>PORT</dd>
       </dl>
<div class="maquee">
<ul id="rt-attack-list">
<li>
       <div>15:23:45</div>
       <div>shandong</div>
       <div>192.188.87.208</div>
<div>smtp</div>
       <div>44</div>
     

   </li>
   <li>
       <div>15:23:45</div>
       <div>shandong</div>
       <div>192.188.87.208</div>
<div>smtp</div>
       <div>44</div>
  
   </li>
  <li>
       <div>15:23:45</div>
       <div>shandong</div>
       <div>192.188.87.208</div>
<div>smtp</div>
       <div>44</div>
    

   </li>
   <li>
       <div>15:23:45</div>
       <div>shandong</div>
       <div>192.188.87.208</div>
<div>smtp</div>
       <div>44</div>
      
   </li>
   <li>
       <div>15:23:45</div>
       <div>shandong</div>
       <div>192.188.87.208</div>
<div>smtp</div>
       <div>44</div>
     

   </li>
   <li>
       <div>15:23:45</div>
       <div>shandong</div>
       <div>192.188.87.208</div>
<div>smtp</div>
       <div>44</div>
   </li>
   <li>
       <div>15:23:45</div>
       <div>shandong</div>
       <div>192.188.87.208</div>
<div>smtp</div>
       <div>48</div>
   </li>
    
</ul>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
<div class="div-d0"><h4><span class="texts">ATTACK TARGETS</span></h4></div>
<div class="div-d">
<ul>
<!-- 攻击地图 -->
<li id="attackd" class="block flex-content" left="0.2%" data-row="4" data-col="1" data-sizex="2" data-sizey="2" style="overflow: hidden;">
<div class="contents" style="overflow: hidden;">
<div id="rt-attackd">
<div class="topRec_Listd">
<dl>
        <dd>#</dd>
        <dd>attack city</dd>
       </dl>
<div class="maqueed">
<ul id="rt-attack-listd">
<!-- <li>
       <div>10</div>
       <div>北京</div>       
   </li>
   <li>
       <div>8</div>
       <div>上海</div>   
   </li>
   <li>
       <div>5</div>
       <div>深圳</div>
   </li>
   <li>
       <div>3</div>
       <div>武汉</div>
   </li>
   <li>
       <div>3</div>
       <div>西安</div>
   </li>
   <li>
       <div>3</div>
       <div>杭州</div>
   </li>
   <li>
       <div>1</div>
       <div>成都</div>
   </li> -->   
</ul>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</body>
</html>

(2)js部分:

(function (factory) {
    if (typeof exports === 'object') {
        // CommonJS
        module.exports = factory(require('jquery'), require('raphael'), require('jquery-mousewheel'));
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery', 'raphael', 'mousewheel'], factory);
    } else {
        // Browser globals
        factory(jQuery, Raphael, jQuery.fn.mousewheel);
    }
}(function ($, Raphael, mousewheel, undefined) {


    "use strict";


    // The plugin name (used on several places)
    var pluginName = "mapael";


    // Version number of jQuery Mapael. See http://semver.org/ for more information.
    var version = "2.1.0";


    /*
     * Mapael constructor
     * Init instance vars and call init()
     * @param container the DOM element on which to apply the plugin
     * @param options the complete options to use
     */
    var Mapael = function (container, options) {
        var self = this;


        // the global container (DOM element object)
        self.container = container;


        // the global container (jQuery object)
        self.$container = $(container);


        // the global options
        self.options = self.extendDefaultOptions(options);


        // zoom TimeOut handler (used to set and clear)
        self.zoomTO = 0;


        // zoom center coordinate (set at touchstart)
        self.zoomCenterX = 0;
        self.zoomCenterY = 0;


        // Zoom pinch (set at touchstart and touchmove)
        self.previousPinchDist = 0;


        // Zoom data
        self.zoomData = {
            zoomLevel: 0,
            zoomX: 0,
            zoomY: 0,
            panX: 0,
            panY: 0
        };


        // resize TimeOut handler (used to set and clear)
        self.resizeTO = 0;


        // Panning: tell if panning action is in progress
        self.panning = false;


        // Panning TimeOut handler (used to set and clear)
        self.panningTO = 0;


        // Animate view box Interval handler (used to set and clear)
        self.animationIntervalID = null;


        // Map subcontainer jQuery object
        self.$map = $("." + self.options.map.cssClass, self.container);


        // Save initial HTML content (used by destroy method)
        self.initialMapHTMLContent = self.$map.html();


        // Allow to store legend containers and initial contents (used by destroy method)
        self.createdLegends = {};


        // The tooltip jQuery object
        self.$tooltip = {};


        // The paper Raphael object
        self.paper = {};


        // The areas object list
        self.areas = {};


        // The plots object list
        self.plots = {};


        // The links object list
        self.links = {};


        // The map configuration object (taken from map file)
        self.mapConf = {};


        // Let's start the initialization
        self.init();
    };


    /*
     * Mapael Prototype
     * Defines all methods and properties needed by Mapael
     * Each mapael object inherits their properties and methods from this prototype
     */
    Mapael.prototype = {


        /*
         * Version number
         */
        version: version,


        /*
         * Initialize the plugin
         * Called by the constructor
         */
        init: function () {
            var self = this;


            // Init check for class existence
            if (self.options.map.cssClass === "" || $("." + self.options.map.cssClass, self.container).length === 0) {
                throw new Error("The map class `" + self.options.map.cssClass + "` doesn't exists");
            }


            // Create the tooltip container
            self.$tooltip = $("<div>").addClass(self.options.map.tooltip.cssClass).css("display", "none");


            // Get the map container, empty it then append tooltip
            self.$map.empty().append(self.$tooltip);


            // Get the map from $.mapael or $.fn.mapael (backward compatibility)
            if ($[pluginName] && $[pluginName].maps && $[pluginName].maps[self.options.map.name]) {
                // Mapael version >= 2.x
                self.mapConf = $[pluginName].maps[self.options.map.name];
            } else if ($.fn[pluginName] && $.fn[pluginName].maps && $.fn[pluginName].maps[self.options.map.name]) {
                // Mapael version <= 1.x - DEPRECATED
                self.mapConf = $.fn[pluginName].maps[self.options.map.name];
                if (window.console && window.console.warn) {
                    window.console.warn("Extending $.fn.mapael is deprecated (map '" + self.options.map.name + "')");
                }
            } else {
                throw new Error("Unknown map '" + self.options.map.name + "'");
            }
            // Create Raphael paper
            self.paper = new Raphael(self.$map[0], self.mapConf.width, self.mapConf.height);


            // issue #135: Check for Raphael bug on text element boundaries
            if (self.isRaphaelBBoxBugPresent() === true) {
                self.destroy();
                throw new Error("Can't get boundary box for text (is your container hidden? See #135)");
            }

            // add plugin class name on element
            self.$container.addClass(pluginName);


            if (self.options.map.tooltip.css) self.$tooltip.css(self.options.map.tooltip.css);
            self.paper.setViewBox(-360, -50, self.mapConf.width, self.mapConf.height, false);


            // Handle map size
            if (self.options.map.width) {
                // NOT responsive: map has a fixed width
                self.paper.setSize(self.options.map.width, self.mapConf.height * (self.options.map.width / self.mapConf.width));


                // Create the legends for plots taking into account the scale of the map
                self.createLegends("plot", self.plots, (self.options.map.width / self.mapConf.width));
            } else {
                // Responsive: handle resizing of the map
                self.handleMapResizing();
            }


            // Draw map areas
            $.each(self.mapConf.elems, function (id) {
                var elemOptions = self.getElemOptions(
                    self.options.map.defaultArea,
                    (self.options.areas[id] ? self.options.areas[id] : {}),
                    self.options.legend.area
                );
                self.areas[id] = {"mapElem": self.paper.path(self.mapConf.elems[id]).attr(elemOptions.attrs)};
            });
//self.mapConf.elems.attr({"fill":"red"})


            // Hook that allows to add custom processing on the map
            if (self.options.map.beforeInit) self.options.map.beforeInit(self.$container, self.paper, self.options);


            // Init map areas in a second loop (prevent texts to be hidden by map elements)
            $.each(self.mapConf.elems, function (id) {
                var elemOptions = self.getElemOptions(
                    self.options.map.defaultArea,
                    (self.options.areas[id] ? self.options.areas[id] : {}),
                    self.options.legend.area
                );
                self.initElem(self.areas[id], elemOptions, id);
            });


            // Draw links
            self.links = self.drawLinksCollection(self.options.links);


            // Draw plots
            $.each(self.options.plots, function (id) {
                self.plots[id] = self.drawPlot(id);
            });


            // Attach zoom event
            self.$container.on("zoom." + pluginName, function (e, zoomOptions) {
                self.onZoomEvent(e, zoomOptions);
            });


            if (self.options.map.zoom.enabled) {
                // Enable zoom
                self.initZoom(self.mapConf.width-380, self.mapConf.height, self.options.map.zoom);
            }


            // Set initial zoom
            if (self.options.map.zoom.init !== undefined) {
                if (self.options.map.zoom.init.animDuration === undefined) {
                    self.options.map.zoom.init.animDuration = 1;
                }
                self.$container.trigger("zoom", self.options.map.zoom.init);
            }


            // Create the legends for areas
            self.createLegends("area", self.areas, 1);


            // Attach update event
            self.$container.on("update." + pluginName, function (e, opt) {
                self.onUpdateEvent(e, opt);
            });


            // Attach showElementsInRange event
            self.$container.on("showElementsInRange." + pluginName, function (e, opt) {
                self.onShowElementsInRange(e, opt);
            });


            // Hook that allows to add custom processing on the map
            if (self.options.map.afterInit) self.options.map.afterInit(self.$container, self.paper, self.areas, self.plots, self.options);


            $(self.paper.desc).append(" and Mapael " + self.version + " (https://www.vincentbroute.fr/mapael/)");
        },


        /*
         * Destroy mapael
         * This function effectively detach mapael from the container
         *   - Set the container back to the way it was before mapael instanciation
         *   - Remove all data associated to it (memory can then be free'ed by browser)
         *
         * This method can be call directly by user:
         *     $(".mapcontainer").data("mapael").destroy();
         *
         * This method is also automatically called if the user try to call mapael
         * on a container already containing a mapael instance
         */
        destroy: function () {
            var self = this;


            // Detach all event listeners attached to the container
            self.$container.off("." + pluginName);
            self.$map.off("." + pluginName);


            // Detach the global resize event handler
            if (self.onResizeEvent) $(window).off("resize." + pluginName, self.onResizeEvent);


            // Empty the container (this will also detach all event listeners)
            self.$map.empty();


            // Replace initial HTML content
            self.$map.html(self.initialMapHTMLContent);


            // Empty legend containers and replace initial HTML content
            for (var id in self.createdLegends) {
                self.createdLegends[id].container.empty();
                self.createdLegends[id].container.html(self.createdLegends[id].initialHTMLContent);
            }


            // Remove mapael class
            self.$container.removeClass(pluginName);


            // Remove the data
            self.$container.removeData(pluginName);


            // Remove all internal reference
            self.container = undefined;
            self.$container = undefined;
            self.options = undefined;
            self.paper = undefined;
            self.$map = undefined;
            self.$tooltip = undefined;
            self.mapConf = undefined;
            self.areas = undefined;
            self.plots = undefined;
            self.links = undefined;
        },


        handleMapResizing: function () {
            var self = this;


            // onResizeEvent: call when the window element trigger the resize event
            // We create it inside this function (and not in the prototype) in order to have a closure
            // Otherwise, in the prototype, 'this' when triggered is *not* the mapael object but the global window
            self.onResizeEvent = function () {
                // Clear any previous setTimeout (avoid too much triggering)
                clearTimeout(self.resizeTO);
                // setTimeout to wait for the user to finish its resizing
                self.resizeTO = setTimeout(function () {
                    self.$map.trigger("resizeEnd");
                }, 150);
            };


            // Attach resize handler
            $(window).on("resize." + pluginName, self.onResizeEvent);


            // Attach resize end handler, and call it once
            self.$map.on("resizeEnd." + pluginName, function (e, isInit) {
                var containerWidth = self.$map.width();


                if (self.paper.width != containerWidth) {
                    var newScale = containerWidth / self.mapConf.width;
                    // Set new size
                    self.paper.setSize(containerWidth, self.mapConf.height * newScale);


                    // Create plots legend again to take into account the new scale
                    if (isInit || self.options.legend.redrawOnResize) {
                        self.createLegends("plot", self.plots, newScale);
                    }
                }
            }).trigger("resizeEnd", [true]);
        },


        /*
         * Extend the user option with the default one
         * @param options the user options
         * @return new options object
         */
        extendDefaultOptions: function (options) {


            // Extend default options with user options
            options = $.extend(true, {}, Mapael.prototype.defaultOptions, options);


            // Extend legend default options
            $.each(['area', 'plot'], function (key, type) {
                if ($.isArray(options.legend[type])) {
                    for (var i = 0; i < options.legend[type].length; ++i)
                        options.legend[type][i] = $.extend(true, {}, Mapael.prototype.legendDefaultOptions[type], options.legend[type][i]);
                } else {
                    options.legend[type] = $.extend(true, {}, Mapael.prototype.legendDefaultOptions[type], options.legend[type]);
                }
            });


            return options;
        },


        /*
         * Init the element "elem" on the map (drawing, setting attributes, events, tooltip, ...)
         */
        initElem: function (elem, elemOptions, id) {
            var self = this;
            var bbox = {};
            var textPosition = {};


            // Assign value attribute to element
            if (elemOptions.value !== undefined){
                elem.value = elemOptions.value;
            }


            // Init the label related to the element
            if (elemOptions.text && elemOptions.text.content !== undefined) {
                // Set a text label in the area
                bbox = elem.mapElem.getBBox();
                textPosition = self.getTextPosition(bbox, elemOptions.text.position, elemOptions.text.margin);
                elemOptions.text.attrs["text-anchor"] = textPosition.textAnchor;
                elem.textElem = self.paper.text(textPosition.x, textPosition.y, elemOptions.text.content).attr(elemOptions.text.attrs);
                $(elem.textElem.node).attr("data-id", id);
            }


            // Set user event handlers
            if (elemOptions.eventHandlers) self.setEventHandlers(id, elemOptions, elem.mapElem, elem.textElem);


            // Set hover option for mapElem
            self.setHoverOptions(elem.mapElem, elemOptions.attrs, elemOptions.attrsHover);


            // Set hover option for textElem
            if (elem.textElem) self.setHoverOptions(elem.textElem, elemOptions.text.attrs, elemOptions.text.attrsHover);


            // Set hover behavior only if attrsHover is set for area or for text
            if (($.isEmptyObject(elemOptions.attrsHover) === false) ||
                (elem.textElem && $.isEmptyObject(elemOptions.text.attrsHover) === false)) {
                // Set hover behavior
                self.setHover(elem.mapElem, elem.textElem);
            }


            // Init the tooltip
            if (elemOptions.tooltip) {
                elem.mapElem.tooltip = elemOptions.tooltip;
                self.setTooltip(elem.mapElem);


                if (elemOptions.text && elemOptions.text.content !== undefined) {
                    elem.textElem.tooltip = elemOptions.tooltip;
                    self.setTooltip(elem.textElem);
                }
            }


            // Init the link
            if (elemOptions.href) {
                elem.mapElem.href = elemOptions.href;
                elem.mapElem.target = elemOptions.target;
                self.setHref(elem.mapElem);


                if (elemOptions.text && elemOptions.text.content !== undefined) {
                    elem.textElem.href = elemOptions.href;
                    elem.textElem.target = elemOptions.target;
                    self.setHref(elem.textElem);
                }
            }


            if (elemOptions.cssClass !== undefined) {
                $(elem.mapElem.node).addClass(elemOptions.cssClass);
            }


            $(elem.mapElem.node).attr("data-id", id);
        },


        /*
         * Init zoom and panning for the map
         * @param mapWidth
         * @param mapHeight
         * @param zoomOptions
         */
        initZoom: function (mapWidth, mapHeight, zoomOptions) {
            var self = this;
            var mousedown = false;
            var previousX = 0;
            var previousY = 0;
            var fnZoomButtons = {
                "reset": function () {
                    self.$container.trigger("zoom", {"level": 1});
                },
                "in": function () {
                    self.$container.trigger("zoom", {"level": "+1"});
                },
                "out": function () {
                    self.$container.trigger("zoom", {"level": -1});
                }
            };
             self.$container.trigger("zoom", {"level": 1});
            // init Zoom data
            $.extend(self.zoomData, {
                zoomLevel: 2,
                panX: 0,
                panY: 0
            });


            // init zoom buttons
            $.each(zoomOptions.buttons, function(type, opt) {
                if (fnZoomButtons[type] === undefined) throw new Error("Unknown zoom button '" + type + "'");
                // Create div with classes, contents and title (for tooltip)
                var $button = $("<div>").addClass(opt.cssClass)
                    .html(opt.content)
                    .attr("title", opt.title);
                // Assign click event
                $button.on("click." + pluginName, fnZoomButtons[type]);
                // Append to map
                self.$map.append($button);
            });


            // Update the zoom level of the map on mousewheel
            if (self.options.map.zoom.mousewheel) {
                self.$map.on("mousewheel." + pluginName, function (e) {
                    var zoomLevel = (e.deltaY > 0) ? 1 : -1;
                    var coord = self.mapPagePositionToXY(e.pageX, e.pageY);


                    self.$container.trigger("zoom", {
                        "fixedCenter": true,
                        "level": self.zoomData.zoomLevel + zoomLevel,
                        "x": coord.x,
                        "y": coord.y
                    });


                    e.preventDefault();
                });
            }


            // Update the zoom level of the map on touch pinch
            if (self.options.map.zoom.touch) {
                self.$map.on("touchstart." + pluginName, function (e) {
                    if (e.originalEvent.touches.length === 2) {
                        self.zoomCenterX = (e.originalEvent.touches[0].pageX + e.originalEvent.touches[1].pageX) / 2;
                        self.zoomCenterY = (e.originalEvent.touches[0].pageY + e.originalEvent.touches[1].pageY) / 2;
                        self.previousPinchDist = Math.sqrt(Math.pow((e.originalEvent.touches[1].pageX - e.originalEvent.touches[0].pageX), 2) + Math.pow((e.originalEvent.touches[1].pageY - e.originalEvent.touches[0].pageY), 2));
                    }
                });


                self.$map.on("touchmove." + pluginName, function (e) {
                    var pinchDist = 0;
                    var zoomLevel = 0;


                    if (e.originalEvent.touches.length === 2) {
                        pinchDist = Math.sqrt(Math.pow((e.originalEvent.touches[1].pageX - e.originalEvent.touches[0].pageX), 2) + Math.pow((e.originalEvent.touches[1].pageY - e.originalEvent.touches[0].pageY), 2));


                        if (Math.abs(pinchDist - self.previousPinchDist) > 15) {
                            var coord = self.mapPagePositionToXY(self.zoomCenterX, self.zoomCenterY);
                            zoomLevel = (pinchDist - self.previousPinchDist) / Math.abs(pinchDist - self.previousPinchDist);
                            self.$container.trigger("zoom", {
                                "fixedCenter": true,
                                "level": self.zoomData.zoomLevel + zoomLevel,
                                "x": coord.x,
                                "y": coord.y
                            });
                            self.previousPinchDist = pinchDist;
                        }
                        return false;
                    }
                });
            }


            // When the user drag the map, prevent to move the clicked element instead of dragging the map (behaviour seen with Firefox)
            self.$map.on("dragstart", function() {
                return false;
            });


            // Panning
            $("body").on("mouseup." + pluginName + (zoomOptions.touch ? " touchend." + pluginName : ""), function () {
                mousedown = false;
                setTimeout(function () {
                    self.panning = false;
                }, 50);
            });


            self.$map.on("mousedown." + pluginName + (zoomOptions.touch ? " touchstart." + pluginName : ""), function (e) {
                if (e.pageX !== undefined) {
                    mousedown = true;
                    previousX = e.pageX;
                    previousY = e.pageY;
                } else {
                    if (e.originalEvent.touches.length === 1) {
                        mousedown = true;
                        previousX = e.originalEvent.touches[0].pageX;
                        previousY = e.originalEvent.touches[0].pageY;
                    }
                }
            }).on("mousemove." + pluginName + (zoomOptions.touch ? " touchmove." + pluginName : ""), function (e) {
                var currentLevel = self.zoomData.zoomLevel;
                var pageX = 0;
                var pageY = 0;


                if (e.pageX !== undefined) {
                    pageX = e.pageX;
                    pageY = e.pageY;
                } else {
                    if (e.originalEvent.touches.length === 1) {
                        pageX = e.originalEvent.touches[0].pageX;
                        pageY = e.originalEvent.touches[0].pageY;
                    } else {
                        mousedown = false;
                    }
                }


                if (mousedown && currentLevel !== 0) {
                    var offsetX = (previousX - pageX) / (1 + (currentLevel * zoomOptions.step)) * (mapWidth / self.paper.width);
                    var offsetY = (previousY - pageY) / (1 + (currentLevel * zoomOptions.step)) * (mapHeight / self.paper.height);
                    var panX = Math.min(Math.max(0, self.paper._viewBox[0] + offsetX), (mapWidth - self.paper._viewBox[2]));
                    var panY = Math.min(Math.max(0, self.paper._viewBox[1] + offsetY), (mapHeight - self.paper._viewBox[3]));


                    if (Math.abs(offsetX) > 5 || Math.abs(offsetY) > 5) {
                        $.extend(self.zoomData, {
                            panX: panX,
                            panY: panY,
                            zoomX: panX + self.paper._viewBox[2] / 2,
                            zoomY: panY + self.paper._viewBox[3] / 2
                        });
                        self.paper.setViewBox(panX, panY, self.paper._viewBox[2], self.paper._viewBox[3]);


                        clearTimeout(self.panningTO);
                        self.panningTO = setTimeout(function () {
                            self.$map.trigger("afterPanning", {
                                x1: panX,
                                y1: panY,
                                x2: (panX + self.paper._viewBox[2]),
                                y2: (panY + self.paper._viewBox[3])
                            });
                        }, 150);


                        previousX = pageX;
                        previousY = pageY;
                        self.panning = true;
                    }
                    return false;
                }
            });
        },


        /*
         * Map a mouse position to a map position
         *      Transformation principle:
         *          ** start with (pageX, pageY) absolute mouse coordinate
         *          - Apply translation: take into accounts the map offset in the page
         *          ** from this point, we have relative mouse coordinate
         *          - Apply homothetic transformation: take into accounts initial factor of map sizing (fullWidth / actualWidth)
         *          - Apply homothetic transformation: take into accounts the zoom factor
         *          ** from this point, we have relative map coordinate
         *          - Apply translation: take into accounts the current panning of the map
         *          ** from this point, we have absolute map coordinate
         * @param pageX: mouse client coordinate on X
         * @param pageY: mouse client coordinate on Y
         * @return map coordinate {x, y}
         */
        mapPagePositionToXY: function(pageX, pageY) {
            var self = this;
            var offset = self.$map.offset();
            var initFactor = (self.options.map.width) ? (self.mapConf.width / self.options.map.width) : (self.mapConf.width / self.$map.width());
            var zoomFactor = 1 / (1 + (self.zoomData.zoomLevel * self.options.map.zoom.step));
            return {
                x: (zoomFactor * initFactor * (pageX - offset.left)) + self.zoomData.panX,
                y: (zoomFactor * initFactor * (pageY - offset.top)) + self.zoomData.panY
            };
        },


        /*
         * Zoom on the map at a specific level focused on specific coordinates
         * If no coordinates are specified, the zoom will be focused on the center of the map
         * options :
         *    "level" : level of the zoom between minLevel and maxLevel
         *    "x" or "latitude" : x coordinate or latitude of the point to focus on
         *    "y" or "longitude" : y coordinate or longitude of the point to focus on
         *    "fixedCenter" : set to true in order to preserve the position of x,y in the canvas when zoomed
         *    "animDuration" : zoom duration
         */
        onZoomEvent: function (e, zoomOptions) {
            var self = this;
            var newLevel = self.zoomData.zoomLevel;
//alert(newLevel)
            var panX = 0;
            var panY = 0;
            var previousZoomLevel = (1 + self.zoomData.zoomLevel * self.options.map.zoom.step);
            var zoomLevel = 0;
            var animDuration = (zoomOptions.animDuration !== undefined) ? zoomOptions.animDuration : self.options.map.zoom.animDuration;
            var offsetX = 0;
            var offsetY = 0;
            var coords = {};


            // Get user defined zoom level
            if (zoomOptions.level !== undefined) {
                if (typeof zoomOptions.level === "string") {
                    // level is a string, either "n", "+n" or "-n"
                    if ((zoomOptions.level.slice(0, 1) === '+') || (zoomOptions.level.slice(0, 1) === '-')) {
                        // zoomLevel is relative
                        newLevel = self.zoomData.zoomLevel + parseInt(zoomOptions.level);
                    } else {
                        // zoomLevel is absolute
                        newLevel = parseInt(zoomOptions.level);
                    }
                } else {
                    // level is integer
                    if (zoomOptions.level < 0) {
                        // zoomLevel is relative
                        newLevel = self.zoomData.zoomLevel + zoomOptions.level;
                    } else {
                        // zoomLevel is absolute
                        newLevel = zoomOptions.level;
                    }
                }
                // Make sure we stay in the boundaries
                newLevel = Math.min(Math.max(newLevel, self.options.map.zoom.minLevel), self.options.map.zoom.maxLevel);
            }


            zoomLevel = (1 + newLevel * self.options.map.zoom.step);


            if (zoomOptions.latitude !== undefined && zoomOptions.longitude !== undefined) {
                coords = self.mapConf.getCoords(zoomOptions.latitude, zoomOptions.longitude);
                zoomOptions.x = coords.x;
                zoomOptions.y = coords.y;
            }


            if (zoomOptions.x === undefined)
                zoomOptions.x = self.paper._viewBox[0] + self.paper._viewBox[2] / 2;


            if (zoomOptions.y === undefined)
                zoomOptions.y = (self.paper._viewBox[1] + self.paper._viewBox[3] / 2);


            if (newLevel === 0) {
                panX = -250;
                panY = -20;
            } else if (zoomOptions.fixedCenter !== undefined && zoomOptions.fixedCenter === true) {
                offsetX = self.zoomData.panX + ((zoomOptions.x - self.zoomData.panX) * (zoomLevel - previousZoomLevel)) / zoomLevel;
                offsetY = self.zoomData.panY + ((zoomOptions.y - self.zoomData.panY) * (zoomLevel - previousZoomLevel)) / zoomLevel;


                panX =-250+Math.min(Math.max(0, offsetX), (self.mapConf.width - (self.mapConf.width / zoomLevel)));
                panY =-20+Math.min(Math.max(0, offsetY), (self.mapConf.height - (self.mapConf.height / zoomLevel)));
            } else {
                panX = -250+Math.min(Math.max(0, zoomOptions.x - (self.mapConf.width / zoomLevel) / 2), (self.mapConf.width - (self.mapConf.width / zoomLevel)));
                panY = -20+Math.min(Math.max(0, zoomOptions.y - (self.mapConf.height / zoomLevel) / 2), (self.mapConf.height - (self.mapConf.height / zoomLevel)));
            }


            // Update zoom level of the map
            if (zoomLevel == previousZoomLevel && panX == self.zoomData.panX && panY == self.zoomData.panY) return;


            if (animDuration > 0) {
                self.animateViewBox(panX, panY, self.mapConf.width / zoomLevel, self.mapConf.height / zoomLevel, animDuration, self.options.map.zoom.animEasing);
            } else {
                self.paper.setViewBox(panX, panY, self.mapConf.width / zoomLevel, self.mapConf.height / zoomLevel);
                clearTimeout(self.zoomTO);
                self.zoomTO = setTimeout(function () {
                    self.$map.trigger("afterZoom", {
                        x1: panX,
                        y1: panY,
                        x2: (panX + (self.mapConf.width / zoomLevel)),
                        y2: (panY + (self.mapConf.height / zoomLevel))
                    });
                }, 150);
            }


            $.extend(self.zoomData, {
                zoomLevel: newLevel,
                panX: panX,
                panY: panY,
                zoomX: panX + self.paper._viewBox[2] / 2,
                zoomY: panY + self.paper._viewBox[3] / 2
            });
        },


        /*
         * Show some element in range defined by user
         * Triggered by user $(".mapcontainer").trigger("showElementsInRange", [opt]);
         *
         * @param opt the options
         *  opt.hiddenOpacity opacity for hidden element (default = 0.3)
         *  opt.animDuration animation duration in ms (default = 0)
         *  opt.afterShowRange callback
         *  opt.ranges the range to show:
         *  Example:
         *  opt.ranges = {
         *      'plot' : {
         *          0 : {                        // valueIndex
         *              'min': 1000,
         *              'max': 1200
         *          },
         *          1 : {                        // valueIndex
         *              'min': 10,
         *              'max': 12
         *          }
         *      },
         *      'area' : {
         *          {'min': 10, 'max': 20}    // No valueIndex, only an object, use 0 as valueIndex (easy case)
         *      }
         *  }
         */
        onShowElementsInRange: function(e, opt) {
            var self = this;


            // set animDuration to default if not defined
            if (opt.animDuration === undefined) {
                opt.animDuration = 0;
            }


            // set hiddenOpacity to default if not defined
            if (opt.hiddenOpacity === undefined) {
                opt.hiddenOpacity = 0.3;
            }


            // handle area
            if (opt.ranges && opt.ranges.area) {
                self.showElemByRange(opt.ranges.area, self.areas, opt.hiddenOpacity, opt.animDuration);
            }


            // handle plot
            if (opt.ranges && opt.ranges.plot) {
                self.showElemByRange(opt.ranges.plot, self.plots, opt.hiddenOpacity, opt.animDuration);
            }


            // handle link
            if (opt.ranges && opt.ranges.link) {
                self.showElemByRange(opt.ranges.link, self.links, opt.hiddenOpacity, opt.animDuration);
            }


            // Call user callback
            if (opt.afterShowRange) opt.afterShowRange();
        },


        /*
         * Show some element in range
         * @param ranges: the ranges
         * @param elems: list of element on which to check against previous range
         * @hiddenOpacity: the opacity when hidden
         * @animDuration: the animation duration
         */
        showElemByRange: function(ranges, elems, hiddenOpacity, animDuration) {
            var self = this;
            // Hold the final opacity value for all elements consolidated after applying each ranges
            // This allow to set the opacity only once for each elements
            var elemsFinalOpacity = {};


            // set object with one valueIndex to 0 if we have directly the min/max
            if (ranges.min !== undefined || ranges.max !== undefined) {
                ranges = {0: ranges};
            }


            // Loop through each valueIndex
            $.each(ranges, function (valueIndex) {
                var range = ranges[valueIndex];
                // Check if user defined at least a min or max value
                if (range.min === undefined && range.max === undefined) {
                    return true; // skip this iteration (each loop), goto next range
                }
                // Loop through each elements
                $.each(elems, function (id) {
                    var elemValue = elems[id].value;
                    // set value with one valueIndex to 0 if not object
                    if (typeof elemValue !== "object") {
                        elemValue = [elemValue];
                    }
                    // Check existence of this value index
                    if (elemValue[valueIndex] === undefined) {
                        return true; // skip this iteration (each loop), goto next element
                    }
                    // Check if in range
                    if ((range.min !== undefined && elemValue[valueIndex] < range.min) ||
                        (range.max !== undefined && elemValue[valueIndex] > range.max)) {
                        // Element not in range
                        elemsFinalOpacity[id] = hiddenOpacity;
                    } else {
                        // Element in range
                        elemsFinalOpacity[id] = 1;
                    }
                });
            });
            // Now that we looped through all ranges, we can really assign the final opacity
            $.each(elemsFinalOpacity, function (id) {
                self.setElementOpacity(elems[id], elemsFinalOpacity[id], animDuration);
            });
        },


        /*
         * Set element opacity
         * Handle elem.mapElem and elem.textElem
         * @param elem the element
         * @param opacity the opacity to apply
         * @param animDuration the animation duration to use
         */
        setElementOpacity: function(elem, opacity, animDuration) {
            // Ensure no animation is running
            //elem.mapElem.stop();
            //if (elem.textElem) elem.textElem.stop();


            // If final opacity is not null, ensure element is shown before proceeding
            if (opacity > 0) {
                elem.mapElem.show();
                if (elem.textElem) elem.textElem.show();
            }
            if (animDuration > 0) {
                // Animate attribute
                elem.mapElem.animate({"opacity": opacity}, animDuration, "linear", function () {
                    // If final attribute is 0, hide
                    if (opacity === 0) elem.mapElem.hide();
                });
                // Handle text element
                if (elem.textElem) {
                    // Animate attribute
                    elem.textElem.animate({"opacity": opacity}, animDuration, "linear", function () {
                        // If final attribute is 0, hide
                        if (opacity === 0) elem.textElem.hide();
                    });
                }
            } else {
                // Set attribute
                elem.mapElem.attr({"opacity": opacity});
                // For null opacity, hide it
                if (opacity === 0) elem.mapElem.hide();
                // Handle text elemen
                if (elem.textElem) {
                    // Set attribute
                    elem.textElem.attr({"opacity": opacity});
                    // For null opacity, hide it
                    if (opacity === 0) elem.textElem.hide();
                }
            }
        },


        /*
         *
         * Update the current map
         * Refresh attributes and tooltips for areas and plots
         * @param opt option for the refresh :
         *  opt.mapOptions: options to update for plots and areas
         *  opt.replaceOptions: whether mapsOptions should entirely replace current map options, or just extend it
         *  opt.opt.newPlots new plots to add to the map
         *  opt.newLinks new links to add to the map
         *  opt.deletePlotKeys plots to delete from the map (array, or "all" to remove all plots)
         *  opt.deleteLinkKeys links to remove from the map (array, or "all" to remove all links)
         *  opt.setLegendElemsState the state of legend elements to be set : show (default) or hide
         *  opt.animDuration animation duration in ms (default = 0)
         *  opt.afterUpdate Hook that allows to add custom processing on the map
         */
        onUpdateEvent: function (e, opt) {
            var self = this;
            // Abort if opt is undefined
            if (typeof opt !== "object")  return;


            var i = 0;
            var animDuration = (opt.animDuration) ? opt.animDuration : 0;


            // This function remove an element using animation (or not, depending on animDuration)
            // Used for deletePlotKeys and deleteLinkKeys
            var fnRemoveElement = function (elem) {
                // Unset all event handlers
                self.unsetHover(elem.mapElem, elem.textElem);
                if (animDuration > 0) {
                    elem.mapElem.animate({"opacity": 0}, animDuration, "linear", function () {
                        elem.mapElem.remove();
                    });
                    if (elem.textElem) {
                        elem.textElem.animate({"opacity": 0}, animDuration, "linear", function () {
                            elem.textElem.remove();
                        });
                    }
                } else {
                    elem.mapElem.remove();
                    if (elem.textElem) {
                        elem.textElem.remove();
                    }
                }
            };


            // This function show an element using animation
            // Used for newPlots and newLinks
            var fnShowElement = function (elem) {
                // Starts with hidden elements
                elem.mapElem.attr({opacity: 0});
                if (elem.textElem) elem.textElem.attr({opacity: 0});
                // Set final element opacity
                self.setElementOpacity(
                    elem,
                    (elem.mapElem.originalAttrs.opacity !== undefined) ? elem.mapElem.originalAttrs.opacity : 1,
                    animDuration
                );
            };


            if (typeof opt.mapOptions === "object") {
                if (opt.replaceOptions === true) self.options = self.extendDefaultOptions(opt.mapOptions);
                else $.extend(true, self.options, opt.mapOptions);


                // IF we update areas, plots or legend, then reset all legend state to "show"
                if (opt.mapOptions.areas !== undefined || opt.mapOptions.plots !== undefined || opt.mapOptions.legend !== undefined) {
                    $("[data-type='elem']", self.$container).each(function (id, elem) {
                        if ($(elem).attr('data-hidden') === "1") {
                            // Toggle state of element by clicking
                            $(elem).trigger("click", [false, animDuration]);
                        }
                    });
                }
            }


            // Delete plots by name if deletePlotKeys is array
            if (typeof opt.deletePlotKeys === "object") {
                for (; i < opt.deletePlotKeys.length; i++) {
                    if (self.plots[opt.deletePlotKeys[i]] !== undefined) {
                        fnRemoveElement(self.plots[opt.deletePlotKeys[i]]);
                        delete self.plots[opt.deletePlotKeys[i]];
                    }
                }
                // Delete ALL plots if deletePlotKeys is set to "all"
            } else if (opt.deletePlotKeys === "all") {
                $.each(self.plots, function (id, elem) {
                    fnRemoveElement(elem);
                });
                // Empty plots object
                self.plots = {};
            }


            // Delete links by name if deleteLinkKeys is array
            if (typeof opt.deleteLinkKeys === "object") {
                for (i = 0; i < opt.deleteLinkKeys.length; i++) {
                    if (self.links[opt.deleteLinkKeys[i]] !== undefined) {
                        fnRemoveElement(self.links[opt.deleteLinkKeys[i]]);
                        delete self.links[opt.deleteLinkKeys[i]];
                    }
                }
                // Delete ALL links if deleteLinkKeys is set to "all"
            } else if (opt.deleteLinkKeys === "all") {
                $.each(self.links, function (id, elem) {
                    fnRemoveElement(elem);
                });
                // Empty links object
                self.links = {};
            }


            // New plots
            if (typeof opt.newPlots === "object") {
                $.each(opt.newPlots, function (id) {
                    if (self.plots[id] === undefined) {
                        self.options.plots[id] = opt.newPlots[id];
                        self.plots[id] = self.drawPlot(id);
                        if (animDuration > 0) {
                            fnShowElement(self.plots[id]);
                        }
                    }
                });
            }


            // New links
            if (typeof opt.newLinks === "object") {
                var newLinks = self.drawLinksCollection(opt.newLinks);
                $.extend(self.links, newLinks);
                $.extend(self.options.links, opt.newLinks);
                if (animDuration > 0) {
                    $.each(newLinks, function (id) {
                        fnShowElement(newLinks[id]);
                    });
                }
            }


            // Update areas attributes and tooltips
            $.each(self.areas, function (id) {
                // Avoid updating unchanged elements
                if ((typeof opt.mapOptions === "object" &&
                    (
                        (typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultArea === "object")
                        || (typeof opt.mapOptions.areas === "object" && typeof opt.mapOptions.areas[id] === "object")
                        || (typeof opt.mapOptions.legend === "object" && typeof opt.mapOptions.legend.area === "object")
                    ))
                    || opt.replaceOptions === true
                ) {
                    var elemOptions = self.getElemOptions(
                        self.options.map.defaultArea,
                        (self.options.areas[id] ? self.options.areas[id] : {}),
                        self.options.legend.area
                    );
                    self.updateElem(elemOptions, self.areas[id], animDuration);
                }
            });


            // Update plots attributes and tooltips
            $.each(self.plots, function (id) {
                // Avoid updating unchanged elements
                if ((typeof opt.mapOptions ==="object" &&
                    (
                        (typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultPlot === "object")
                        || (typeof opt.mapOptions.plots === "object" && typeof opt.mapOptions.plots[id] === "object")
                        || (typeof opt.mapOptions.legend === "object" && typeof opt.mapOptions.legend.plot === "object")
                    ))
                    || opt.replaceOptions === true
                ) {
                    var elemOptions = self.getElemOptions(
                        self.options.map.defaultPlot,
                        (self.options.plots[id] ? self.options.plots[id] : {}),
                        self.options.legend.plot
                    );
                    if (elemOptions.type == "square") {
                        elemOptions.attrs.width = elemOptions.size;
                        elemOptions.attrs.height = elemOptions.size;
                        elemOptions.attrs.x = self.plots[id].mapElem.attrs.x - (elemOptions.size - self.plots[id].mapElem.attrs.width) / 2;
                        elemOptions.attrs.y = self.plots[id].mapElem.attrs.y - (elemOptions.size - self.plots[id].mapElem.attrs.height) / 2;
                    } else if (elemOptions.type == "image") {
                        elemOptions.attrs.width = elemOptions.width;
                        elemOptions.attrs.height = elemOptions.height;
                        elemOptions.attrs.x = self.plots[id].mapElem.attrs.x - (elemOptions.width - self.plots[id].mapElem.attrs.width) / 2;
                        elemOptions.attrs.y = self.plots[id].mapElem.attrs.y - (elemOptions.height - self.plots[id].mapElem.attrs.height) / 2;
                    } else if (elemOptions.type == "svg") {
                        if (elemOptions.attrs.transform !== undefined) {
                            elemOptions.attrs.transform = self.plots[id].mapElem.baseTransform + elemOptions.attrs.transform;
                        }
                    }else { // Default : circle
                        elemOptions.attrs.r = elemOptions.size / 2;
                    }


                    self.updateElem(elemOptions, self.plots[id], animDuration);
                }
            });


            // Update links attributes and tooltips
            $.each(self.links, function (id) {
                // Avoid updating unchanged elements
                if ((typeof opt.mapOptions === "object" &&
                    (
                        (typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultLink === "object")
                        || (typeof opt.mapOptions.links === "object" && typeof opt.mapOptions.links[id] === "object")
                    ))
                    || opt.replaceOptions === true
                ) {
                    var elemOptions = self.getElemOptions(
                        self.options.map.defaultLink,
                        (self.options.links[id] ? self.options.links[id] : {}),
                        {}
                    );


                    self.updateElem(elemOptions, self.links[id], animDuration);
                }
            });


            // Update legends
            if (opt.mapOptions && (
                    (typeof opt.mapOptions.legend === "object")
                    || (typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultArea === "object")
                    || (typeof opt.mapOptions.map === "object" && typeof opt.mapOptions.map.defaultPlot === "object")
                )) {
                // Show all elements on the map before updating the legends
                $("[data-type='elem']", self.$container).each(function (id, elem) {
                    if ($(elem).attr('data-hidden') === "1") {
                        $(elem).trigger("click", [false, animDuration]);
                    }
                });


                self.createLegends("area", self.areas, 1);
                if (self.options.map.width) {
                    self.createLegends("plot", self.plots, (self.options.map.width / self.mapConf.width));
                } else {
                    self.createLegends("plot", self.plots, (self.$map.width() / self.mapConf.width));
                }
            }


            // Hide/Show all elements based on showlegendElems
            //      Toggle (i.e. click) only if:
            //          - slice legend is shown AND we want to hide
            //          - slice legend is hidden AND we want to show
            if (typeof opt.setLegendElemsState === "object") {
                // setLegendElemsState is an object listing the legend we want to hide/show
                $.each(opt.setLegendElemsState, function (legendCSSClass, action) {
                    // Search for the legend
                    var $legend = self.$container.find("." + legendCSSClass)[0];
                    if ($legend !== undefined) {
                        // Select all elem inside this legend
                        $("[data-type='elem']", $legend).each(function (id, elem) {
                            if (($(elem).attr('data-hidden') === "0" && action === "hide") ||
                                ($(elem).attr('data-hidden') === "1" && action === "show")) {
                                // Toggle state of element by clicking
                                $(elem).trigger("click", [false, animDuration]);
                            }
                        });
                    }
                });
            } else {
                // setLegendElemsState is a string, or is undefined
                // Default : "show"
                var action = (opt.setLegendElemsState === "hide") ? "hide" : "show";


                $("[data-type='elem']", self.$container).each(function (id, elem) {
                    if (($(elem).attr('data-hidden') === "0" && action === "hide") ||
                        ($(elem).attr('data-hidden') === "1" && action === "show")) {
                        // Toggle state of element by clicking
                        $(elem).trigger("click", [false, animDuration]);
                    }
                });
            }
            if (opt.afterUpdate) opt.afterUpdate(self.$container, self.paper, self.areas, self.plots, self.options);
        },


        /*
         * Draw all links between plots on the paper
         */
        drawLinksCollection: function (linksCollection) {
            var self = this;
            var p1 = {};
            var p2 = {};
            var coordsP1 = {};
            var coordsP2 = {};
            var links = {};


            $.each(linksCollection, function (id) {
                var elemOptions = self.getElemOptions(self.options.map.defaultLink, linksCollection[id], {});
                elemOptions.opacity=0.1;
                if (typeof linksCollection[id].between[0] == 'string') {
                    p1 = self.options.plots[linksCollection[id].between[0]];
                } else {
                    p1 = linksCollection[id].between[0];
                }


                if (typeof linksCollection[id].between[1] == 'string') {
                    p2 = self.options.plots[linksCollection[id].between[1]];
                } else {
                    p2 = linksCollection[id].between[1];
                }


                if (p1.latitude !== undefined && p1.longitude !== undefined) {
                    coordsP1 = self.mapConf.getCoords(p1.latitude, p1.longitude);
                } else {
                    coordsP1.x = p1.x;
                    coordsP1.y = p1.y;
                }


                if (p2.latitude !== undefined && p2.longitude !== undefined) {
                    coordsP2 = self.mapConf.getCoords(p2.latitude, p2.longitude);
                } else {
                    coordsP2.x = p2.x;
                    coordsP2.y = p2.y;
                }
                links[id] = self.drawLink(id, coordsP1.x, coordsP1.y, coordsP2.x, coordsP2.y, elemOptions);
            });
            return links;
        },


        /*
         * Draw a curved link between two couples of coordinates a(xa,ya) and b(xb, yb) on the paper
         */
        drawLink: function (id, xa, ya, xb, yb, elemOptions) {
            var self = this;
            var elem = {};
            // Compute the "curveto" SVG point, d(x,y)
            // c(xc, yc) is the center of (xa,ya) and (xb, yb)
            var xc = (xa + xb) / 2;
            var yc = (ya + yb) / 2;


            // Equation for (cd) : y = acd * x + bcd (d is the cure point)
            var acd = -1 / ((yb - ya) / (xb - xa));
            var bcd = yc - acd * xc;


            // dist(c,d) = dist(a,b) (=abDist)
            var abDist = Math.sqrt((xb - xa) * (xb - xa) + (yb - ya) * (yb - ya));


            // Solution for equation dist(cd) = sqrt((xd - xc)² + (yd - yc)²)
            // dist(c,d)² = (xd - xc)² + (yd - yc)²
            // We assume that dist(c,d) = dist(a,b)
            // so : (xd - xc)² + (yd - yc)² - dist(a,b)² = 0
            // With the factor : (xd - xc)² + (yd - yc)² - (factor*dist(a,b))² = 0
            // (xd - xc)² + (acd*xd + bcd - yc)² - (factor*dist(a,b))² = 0
            var a = 1 + acd * acd;
            var b = -2 * xc + 2 * acd * bcd - 2 * acd * yc;
            var c = xc * xc + bcd * bcd - bcd * yc - yc * bcd + yc * yc - ((elemOptions.factor * abDist) * (elemOptions.factor * abDist));
            var delta = b * b - 4 * a * c;
            var x = 0;
            var y = 0;


            // There are two solutions, we choose one or the other depending on the sign of the factor
            if (elemOptions.factor > 0) {
                x = (-b + Math.sqrt(delta)) / (2 * a);
                y = acd * x + bcd;
            } else {
                x = (-b - Math.sqrt(delta)) / (2 * a);
                y = acd * x + bcd;
            }


            elem.mapElem = self.paper.path("m " + xa + "," + ya + " C " + x + "," + y + " " + xb + "," + yb + " " + xb + "," + yb + "").attr(elemOptions.attrs);
self.paper.addGuides();
var len = elem.mapElem.getTotalLength(),
point=elem.mapElem.getPointAtLength(len);
var e = self.paper.rect(-15, -2.50, 30, 5).attr({stroke: "none", fill:elemOptions.attrs.stroke}); 
e.attr({guide : elem.mapElem,along : 0}) .animate({
"0%" : {along : 0,width:30,length:5,opacity:0},
"10%" : {along : 0,width:30,length:5},
"20%" : {along : 0.2,width:30,length:5},
"50%" : {along : 0.5,width:30,length:5,opacity:2},
"80%" : {along : 0.76,width:30,length:5,opacity:2},
"82%" : {along : 0.78,width:29.9,length:5,opacity:2},
"84%" : {along : 0.80,width:29.69,length:5,opacity:2},
"85%" : {along : 0.81,width:29.39,length:5,opacity:2},
"86%" : {along : 0.82,width:28,length:5,opacity:2},
"88%" : {along : 0.88,width:20,length:10,opacity:0},
//"90%" : {along : 0.86,width:27.19,length:5,opacity:2},
//"92%" : {along : 0.88,width:26.99,length:5,opacity:0.05},
//"94%" : {along : 0.92,width:26.49,length:5,opacity:0.03},
//"95%" : {along : 0.95,width:25,length:5,opacity:0.02},
"100%" : {along : 0.99,width:5,length:5,opacity:0}}, 1000, function(){
e.remove();
var c= self.paper.circle(point.x,point.y,10);
c.attr({"stroke": elemOptions.attrs.stroke,"fill":"#00000000","index":8,"stroke-width":5});
var r = 10;
var p=0.85;
setInterval(function(){
 r*=1.35;  
 p*=0.82;
 c.animate({20:{'r':r,opacity:p}});
 if(r>=50){  //已经透明几乎看不见了
c.remove();//从DOM上删除圆形
 }
},120);
});
            self.initElem(elem, elemOptions, id);


            return elem;
        },


        /*
         * Check wether newAttrs object bring modifications to originalAttrs object
         */
        isAttrsChanged: function(originalAttrs, newAttrs) {
            for (var key in newAttrs) {
                if (typeof originalAttrs[key] === 'undefined' || newAttrs[key] !== originalAttrs[key]) {
                    return true;
                }
            }
            return false;
        },


        /*
         * Update the element "elem" on the map with the new elemOptions options
         */
        updateElem: function (elemOptions, elem, animDuration) {
            var self = this;
            var bbox;
            var textPosition;
            var plotOffsetX;
            var plotOffsetY;


            if (elemOptions.value !== undefined)
                elem.value = elemOptions.value;


            if (elemOptions.toFront === true) {
                elem.mapElem.toFront();
            }


            // Update the label
            if (elem.textElem) {
                if (elemOptions.text !== undefined && elemOptions.text.content !== undefined && elemOptions.text.content != elem.textElem.attrs.text)
                    elem.textElem.attr({text: elemOptions.text.content});


                bbox = elem.mapElem.getBBox();


                if (elemOptions.size || (elemOptions.width && elemOptions.height)) {
                    if (elemOptions.type == "image" || elemOptions.type == "svg") {
                        plotOffsetX = (elemOptions.width - bbox.width) / 2;
                        plotOffsetY = (elemOptions.height - bbox.height) / 2;
                    } else {
                        plotOffsetX = (elemOptions.size - bbox.width) / 2;
                        plotOffsetY = (elemOptions.size - bbox.height) / 2;
                    }
                    bbox.x -= plotOffsetX;
                    bbox.x2 += plotOffsetX;
                    bbox.y -= plotOffsetY;
                    bbox.y2 += plotOffsetY;
                }


                textPosition = self.getTextPosition(bbox, elemOptions.text.position, elemOptions.text.margin);
                if (textPosition.x != elem.textElem.attrs.x || textPosition.y != elem.textElem.attrs.y) {
                    if (animDuration > 0) {
                        elem.textElem.attr({"text-anchor": textPosition.textAnchor});
                        elem.textElem.animate({x: textPosition.x, y: textPosition.y}, animDuration);
                    } else
                        elem.textElem.attr({
                            x: textPosition.x,
                            y: textPosition.y,
                            "text-anchor": textPosition.textAnchor
                        });
                }


                self.setHoverOptions(elem.textElem, elemOptions.text.attrs, elemOptions.text.attrsHover);
                if (animDuration > 0)
                    elem.textElem.animate(elemOptions.text.attrs, animDuration);
                else
                    elem.textElem.attr(elemOptions.text.attrs);
            }


            // Update elements attrs and attrsHover
            self.setHoverOptions(elem.mapElem, elemOptions.attrs, elemOptions.attrsHover);


            if (self.isAttrsChanged(elem.mapElem.attrs, elemOptions.attrs)) {
                if (animDuration > 0)
                    elem.mapElem.animate(elemOptions.attrs, animDuration);
                else
                    elem.mapElem.attr(elemOptions.attrs);
            }


            // Update dimensions of SVG plots
            if (elemOptions.type == "svg") {


                if (bbox === undefined) {
                    bbox = elem.mapElem.getBBox();
                }
                elem.mapElem.transform("m" + (elemOptions.width / elem.mapElem.originalWidth) + ",0,0," + (elemOptions.height / elem.mapElem.originalHeight) + "," + bbox.x + "," + bbox.y);
            }


            // Update the tooltip
            if (elemOptions.tooltip) {
                if (elem.mapElem.tooltip === undefined) {
                    self.setTooltip(elem.mapElem);
                    if (elem.textElem) self.setTooltip(elem.textElem);
                }
                elem.mapElem.tooltip = elemOptions.tooltip;
                if (elem.textElem) elem.textElem.tooltip = elemOptions.tooltip;
            }


            // Update the link
            if (elemOptions.href !== undefined) {
                if (elem.mapElem.href === undefined) {
                    self.setHref(elem.mapElem);
                    if (elem.textElem) self.setHref(elem.textElem);
                }
                elem.mapElem.href = elemOptions.href;
                elem.mapElem.target = elemOptions.target;
                if (elem.textElem) {
                    elem.textElem.href = elemOptions.href;
                    elem.textElem.target = elemOptions.target;
                }
            }
        },


        /*
         * Draw the plot
         */
        drawPlot: function (id) {
            var self = this;
            var plot = {};
            var coords = {};
            var elemOptions = self.getElemOptions(
                self.options.map.defaultPlot,
                (self.options.plots[id] ? self.options.plots[id] : {}),
                self.options.legend.plot
            );


            if (elemOptions.x !== undefined && elemOptions.y !== undefined)
                coords = {x: elemOptions.x, y: elemOptions.y};
            else if (elemOptions.plotsOn !== undefined && self.areas[elemOptions.plotsOn].mapElem !== undefined){
                var path = self.areas[elemOptions.plotsOn].mapElem;
                var bbox = path.getBBox();
                var _x = Math.floor(bbox.x + bbox.width/2.0);
                var _y = Math.floor(bbox.y + bbox.height/2.0);
                coords = {x: _x, y: _y};
            }
            else
                coords = self.mapConf.getCoords(elemOptions.latitude, elemOptions.longitude);


            if (elemOptions.type == "square") {
                plot = {
                    "mapElem": self.paper.rect(
                        coords.x - (elemOptions.size / 2),
                        coords.y - (elemOptions.size / 2),
                        elemOptions.size,
                        elemOptions.size
                    ).attr(elemOptions.attrs)
                };
            } else if (elemOptions.type == "image") {
                plot = {
                    "mapElem": self.paper.image(
                        elemOptions.url,
                        coords.x - elemOptions.width / 2,
                        coords.y - elemOptions.height / 2,
                        elemOptions.width,
                        elemOptions.height
                    ).attr(elemOptions.attrs)
                };
            } else if (elemOptions.type == "svg") {
                if (elemOptions.attrs.transform === undefined) {
                    elemOptions.attrs.transform = "";
                }


                plot = {"mapElem": self.paper.path(elemOptions.path)};
                plot.mapElem.originalWidth = plot.mapElem.getBBox().width;
                plot.mapElem.originalHeight = plot.mapElem.getBBox().height;


                plot.mapElem.baseTransform = "m" + (elemOptions.width / plot.mapElem.originalWidth) + ",0,0," + (elemOptions.height / plot.mapElem.originalHeight) + "," + (coords.x - elemOptions.width / 2) + "," + (coords.y - elemOptions.height / 2);
                elemOptions.attrs.transform = plot.mapElem.baseTransform + elemOptions.attrs.transform;
                plot.mapElem.attr(elemOptions.attrs);
            } else { // Default = circle
                plot = {"mapElem": self.paper.circle(coords.x, coords.y, elemOptions.size / 2).attr(elemOptions.attrs)};
            }
            self.initElem(plot, elemOptions, id);
            return plot;
        },


        /*
         * Set target link on elem
         */
        setHref: function (elem) {
            var self = this;
            elem.attr({cursor: "pointer"});
            $(elem.node).on("click." + pluginName, function () {
                if (!self.panning && elem.href)
                    window.open(elem.href, elem.target);
            });
        },


        /*
         * Set a tooltip for the areas and plots
         * @param elem area or plot element
         * @param content the content to set in the tooltip
         */
        setTooltip: function (elem) {
            var self = this;
            var tooltipTO = 0;
            var cssClass = self.$tooltip.attr('class');






            var updateTooltipPosition = function (x, y) {


                var offsetLeft = 10;
                var offsetTop = 20;


                if (typeof elem.tooltip.offset === "object") {
                    if (typeof elem.tooltip.offset.left !== "undefined") {
                        offsetLeft = elem.tooltip.offset.left;
                    }
                    if (typeof elem.tooltip.offset.top !== "undefined") {
                        offsetTop = elem.tooltip.offset.top;
                    }
                }


                var tooltipPosition = {
                    "left": Math.min(self.$map.width() - self.$tooltip.outerWidth() - 5, x - self.$map.offset().left + offsetLeft),
                    "top": Math.min(self.$map.height() - self.$tooltip.outerHeight() - 5, y - self.$map.offset().top + offsetTop)
                };


                if (typeof elem.tooltip.overflow === "object") {
                    if (elem.tooltip.overflow.right === true) {
                        tooltipPosition.left = x - self.$map.offset().left + 10;
                    }
                    if (selem.tooltip.overflow.bottom === true) {
                        tooltipPosition.top = y - self.$map.offset().top + 20;
                    }
                }


                self.$tooltip.css(tooltipPosition);
            };


            $(elem.node).on("mouseover." + pluginName, function (e) {
                tooltipTO = setTimeout(
                    function () {
                        self.$tooltip.attr("class", cssClass);
                        if (elem.tooltip !== undefined) {
                            if (elem.tooltip.content !== undefined) {
                                // if tooltip.content is function, call it. Otherwise, assign it directly.
                                var content = (typeof elem.tooltip.content === "function") ? elem.tooltip.content(elem) : elem.tooltip.content;
                                self.$tooltip.html(content).css("display", "block");
                            }
                            if (elem.tooltip.cssClass !== undefined) {
                                self.$tooltip.addClass(elem.tooltip.cssClass);
                            }
                        }
                        updateTooltipPosition(e.pageX, e.pageY);
                    }, 120
                );
            }).on("mouseout." + pluginName, function () {
                clearTimeout(tooltipTO);
                self.$tooltip.css("display", "none");
            }).on("mousemove." + pluginName, function (e) {
                updateTooltipPosition(e.pageX, e.pageY);
            });
        },


        /*
         * Set user defined handlers for events on areas and plots
         * @param id the id of the element
         * @param elemOptions the element parameters
         * @param mapElem the map element to set callback on
         * @param textElem the optional text within the map element
         */
        setEventHandlers: function (id, elemOptions, mapElem, textElem) {
            var self = this;
            $.each(elemOptions.eventHandlers, function (event) {
                (function (event) {
                    $(mapElem.node).on(event, function (e) {
                        if (!self.panning) elemOptions.eventHandlers[event](e, id, mapElem, textElem, elemOptions);
                    });
                    if (textElem) {
                        $(textElem.node).on(event, function (e) {
                            if (!self.panning) elemOptions.eventHandlers[event](e, id, mapElem, textElem, elemOptions);
                        });
                    }
                })(event);
            });
        },


        /*
         * Draw a legend for areas and / or plots
         * @param legendOptions options for the legend to draw
         * @param legendType the type of the legend : "area" or "plot"
         * @param elems collection of plots or areas on the maps
         * @param legendIndex index of the legend in the conf array
         */
        drawLegend: function (legendOptions, legendType, elems, scale, legendIndex) {
            var self = this;
            var $legend = {};
            var legendPaper = {};
            var width = 0;
            var height = 0;
            var title = null;
            var elem = {};
            var elemBBox = {};
            var label = {};
            var i = 0;
            var x = 0;
            var y = 0;
            var yCenter = 0;
            var sliceOptions = [];
            var length = 0;


            $legend = $("." + legendOptions.cssClass, self.$container);


            if (typeof self.createdLegends[legendOptions.cssClass] ==='undefined') {
                self.createdLegends[legendOptions.cssClass] = {
                    container: $legend,
                    initialHTMLContent: $legend.html()
                };
            }


            $legend.empty();


            legendPaper = new Raphael($legend.get(0));
            // Set some data to object
            $(legendPaper.canvas).attr({"data-type": legendType, "data-index": legendIndex});


            height = width = 0;


            // Set the title of the legend
            if (legendOptions.title && legendOptions.title !== "") {
                title = legendPaper.text(legendOptions.marginLeftTitle, 0, legendOptions.title).attr(legendOptions.titleAttrs);
                title.attr({y: 0.5 * title.getBBox().height});


                width = legendOptions.marginLeftTitle + title.getBBox().width;
                height += legendOptions.marginBottomTitle + title.getBBox().height;
            }


            // Calculate attrs (and width, height and r (radius)) for legend elements, and yCenter for horizontal legends


            for (i = 0, length = legendOptions.slices.length; i < length; ++i) {
                var yCenterCurrent = 0;


                sliceOptions[i] = $.extend(true, {}, (legendType == "plot") ? self.options.map.defaultPlot : self.options.map.defaultArea, legendOptions.slices[i]);


                if (legendOptions.slices[i].legendSpecificAttrs === undefined) {
                    legendOptions.slices[i].legendSpecificAttrs = {};
                }


                $.extend(true, sliceOptions[i].attrs, legendOptions.slices[i].legendSpecificAttrs);


                if (legendType == "area") {
                    if (sliceOptions[i].attrs.width === undefined)
                        sliceOptions[i].attrs.width = 30;
                    if (sliceOptions[i].attrs.height === undefined)
                        sliceOptions[i].attrs.height = 20;
                } else if (sliceOptions[i].type == "square") {
                    if (sliceOptions[i].attrs.width === undefined)
                        sliceOptions[i].attrs.width = sliceOptions[i].size;
                    if (sliceOptions[i].attrs.height === undefined)
                        sliceOptions[i].attrs.height = sliceOptions[i].size;
                } else if (sliceOptions[i].type == "image" || sliceOptions[i].type == "svg") {
                    if (sliceOptions[i].attrs.width === undefined)
                        sliceOptions[i].attrs.width = sliceOptions[i].width;
                    if (sliceOptions[i].attrs.height === undefined)
                        sliceOptions[i].attrs.height = sliceOptions[i].height;
                } else {
                    if (sliceOptions[i].attrs.r === undefined)
                        sliceOptions[i].attrs.r = sliceOptions[i].size / 2;
                }


                // Compute yCenter for this legend slice
                yCenterCurrent = legendOptions.marginBottomTitle;
                // Add title height if it exists
                if (title) {
                    yCenterCurrent += title.getBBox().height;
                }
                if (legendType == "plot" && (sliceOptions[i].type === undefined || sliceOptions[i].type == "circle")) {
                    yCenterCurrent += scale * sliceOptions[i].attrs.r;
                } else {
                    yCenterCurrent += scale * sliceOptions[i].attrs.height / 2;
                }
                // Update yCenter if current larger
                yCenter = Math.max(yCenter, yCenterCurrent);
            }


            if (legendOptions.mode == "horizontal") {
                width = legendOptions.marginLeft;
            }


            // Draw legend elements (circle, square or image in vertical or horizontal mode)
            for (i = 0, length = sliceOptions.length; i < length; ++i) {
                if (sliceOptions[i].display === undefined || sliceOptions[i].display === true) {
                    if (legendType == "area") {
                        if (legendOptions.mode == "horizontal") {
                            x = width + legendOptions.marginLeft;
                            y = yCenter - (0.5 * scale * sliceOptions[i].attrs.height);
                        } else {
                            x = legendOptions.marginLeft;
                            y = height;
                        }


                        elem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height));
                    } else if (sliceOptions[i].type == "square") {
                        if (legendOptions.mode == "horizontal") {
                            x = width + legendOptions.marginLeft;
                            y = yCenter - (0.5 * scale * sliceOptions[i].attrs.height);
                        } else {
                            x = legendOptions.marginLeft;
                            y = height;
                        }


                        elem = legendPaper.rect(x, y, scale * (sliceOptions[i].attrs.width), scale * (sliceOptions[i].attrs.height));


                    } else if (sliceOptions[i].type == "image" || sliceOptions[i].type == "svg") {
                        if (legendOptions.mode == "horizontal") {
                            x = width + legendOptions.marginLeft;
                            y = yCenter - (0.5 * scale * sliceOptions[i].attrs.height);
                        } else {
                            x = legendOptions.marginLeft;
                            y = height;
                        }


                        if (sliceOptions[i].type == "image") {
                            elem = legendPaper.image(
                                sliceOptions[i].url, x, y, scale * sliceOptions[i].attrs.width, scale * sliceOptions[i].attrs.height);
                        } else {
                            elem = legendPaper.path(sliceOptions[i].path);


                            if (sliceOptions[i].attrs.transform === undefined) {
                                sliceOptions[i].attrs.transform = "";
                            }
                            sliceOptions[i].attrs.transform = "m" + ((scale * sliceOptions[i].width) / elem.getBBox().width) + ",0,0," + ((scale * sliceOptions[i].height) / elem.getBBox().height) + "," + x + "," + y + sliceOptions[i].attrs.transform;
                        }
                    } else {
                        if (legendOptions.mode == "horizontal") {
                            x = width + legendOptions.marginLeft + scale * (sliceOptions[i].attrs.r);
                            y = yCenter;
                        } else {
                            x = legendOptions.marginLeft + scale * (sliceOptions[i].attrs.r);
                            y = height + scale * (sliceOptions[i].attrs.r);
                        }
                        elem = legendPaper.circle(x, y, scale * (sliceOptions[i].attrs.r));
                    }


                    // Set attrs to the element drawn above
                    delete sliceOptions[i].attrs.width;
                    delete sliceOptions[i].attrs.height;
                    delete sliceOptions[i].attrs.r;
                    elem.attr(sliceOptions[i].attrs);
                    elemBBox = elem.getBBox();


                    // Draw the label associated with the element
                    if (legendOptions.mode == "horizontal") {
                        x = width + legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel;
                        y = yCenter;
                    } else {
                        x = legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel;
                        y = height + (elemBBox.height / 2);
                    }


                    label = legendPaper.text(x, y, sliceOptions[i].label).attr(legendOptions.labelAttrs);


                    // Update the width and height for the paper
                    if (legendOptions.mode == "horizontal") {
                        var currentHeight = legendOptions.marginBottom + elemBBox.height;
                        width += legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel + label.getBBox().width;
                        if (sliceOptions[i].type != "image" && legendType != "area") {
                            currentHeight += legendOptions.marginBottomTitle;
                        }
                        // Add title height if it exists
                        if (title) {
                            currentHeight += title.getBBox().height;
                        }
                        height = Math.max(height, currentHeight);
                    } else {
                        width = Math.max(width, legendOptions.marginLeft + elemBBox.width + legendOptions.marginLeftLabel + label.getBBox().width);
                        height += legendOptions.marginBottom + elemBBox.height;
                    }


                    $(elem.node).attr({"data-type": "elem", "data-index": i, "data-hidden": 0});
                    $(label.node).attr({"data-type": "label", "data-index": i, "data-hidden": 0});


                    // Hide map elements when the user clicks on a legend item
                    if (legendOptions.hideElemsOnClick.enabled) {
                        // Hide/show elements when user clicks on a legend element
                        label.attr({cursor: "pointer"});
                        elem.attr({cursor: "pointer"});


                        self.setHoverOptions(elem, sliceOptions[i].attrs, sliceOptions[i].attrs);
                        self.setHoverOptions(label, legendOptions.labelAttrs, legendOptions.labelAttrsHover);
                        self.setHover(elem, label);
                        self.handleClickOnLegendElem(legendOptions, legendOptions.slices[i], label, elem, elems, legendIndex);
                    }
                }
            }


            // VMLWidth option allows you to set static width for the legend
            // only for VML render because text.getBBox() returns wrong values on IE6/7
            if (Raphael.type != "SVG" && legendOptions.VMLWidth)
                width = legendOptions.VMLWidth;


            legendPaper.setSize(width, height);
        },


        /*
         * Allow to hide elements of the map when the user clicks on a related legend item
         * @param legendOptions options for the legend to draw
         * @param sliceOptions options of the slice
         * @param label label of the legend item
         * @param elem element of the legend item
         * @param elems collection of plots or areas displayed on the map
         * @param legendIndex index of the legend in the conf array
         */
        handleClickOnLegendElem: function (legendOptions, sliceOptions, label, elem, elems, legendIndex) {
            var self = this;


            /**
             *
             * @param e
             * @param hideOtherElems : option used for the 'exclusive' mode to enabled only one item from the legend
             * at once
             * @param animDuration : used in the 'update' event in order to apply the same animDuration on the legend items
             */
            var hideMapElems = function (e, hideOtherElems, animDuration) {
                var elemValue = 0;
                var hidden = $(label.node).attr('data-hidden');
                var hiddenNewAttr = (hidden === '0') ? {"data-hidden": '1'} : {"data-hidden": '0'};


                // Check animDuration: if not set, this is a regular click, use the value specified in options
                if (animDuration === undefined) animDuration = legendOptions.hideElemsOnClick.animDuration;


                if (hidden === '0') {
                    if (animDuration > 0) label.animate({"opacity": 0.5}, animDuration);
                    else label.attr({"opacity": 0.5});
                } else {
                    if (animDuration > 0) label.animate({"opacity": 1}, animDuration);
                    else label.attr({"opacity": 1});
                }


                $.each(elems, function (id) {
                    // Retreive stored data of element
                    //      'hidden-by' contains the list of legendIndex that is hiding this element
                    var hiddenBy = elems[id].mapElem.data('hidden-by');
                    // Set to empty object if undefined
                    if (hiddenBy === undefined) hiddenBy = {};


                    if ($.isArray(elems[id].value)) {
                        elemValue = elems[id].value[legendIndex];
                    } else {
                        elemValue = elems[id].value;
                    }


                    // Hide elements whose value matches with the slice of the clicked legend item
                    if (self.getLegendSlice(elemValue, legendOptions) === sliceOptions) {
                        (function (id) {
                            if (hidden === '0') { // we want to hide this element
                                hiddenBy[legendIndex] = true; // add legendIndex to the data object for later use
                                self.setElementOpacity(elems[id], legendOptions.hideElemsOnClick.opacity, animDuration);
                            } else { // We want to show this element
                                delete hiddenBy[legendIndex]; // Remove this legendIndex from object
                                // Check if another legendIndex is defined
                                // We will show this element only if no legend is no longer hiding it
                                if ($.isEmptyObject(hiddenBy)) {
                                    self.setElementOpacity(
                                        elems[id],
                                        elems[id].mapElem.originalAttrs.opacity !== undefined ? elems[id].mapElem.originalAttrs.opacity : 1,
                                        animDuration
                                    );
                                }
                            }
                            // Update elem data with new values
                            elems[id].mapElem.data('hidden-by', hiddenBy);
                        })(id);
                    }
                });


                $(elem.node).attr(hiddenNewAttr);
                $(label.node).attr(hiddenNewAttr);


                if ((hideOtherElems === undefined || hideOtherElems === true)
                    && legendOptions.exclusive !== undefined && legendOptions.exclusive === true
                ) {
                    $("[data-type='elem'][data-hidden=0]", self.$container).each(function () {
                        if ($(this).attr('data-index') !== $(elem.node).attr('data-index')) {
                            $(this).trigger("click", false);
                        }
                    });
                }
            };
            $(label.node).on("click." + pluginName, hideMapElems);
            $(elem.node).on("click." + pluginName, hideMapElems);


            if (sliceOptions.clicked !== undefined && sliceOptions.clicked === true) {
                $(elem.node).trigger("click", false);
            }
        },


        /*
         * Create all legends for a specified type (area or plot)
         * @param legendType the type of the legend : "area" or "plot"
         * @param elems collection of plots or areas displayed on the map
         * @param scale scale ratio of the map
         */
        createLegends: function (legendType, elems, scale) {
            var self = this;
            var legendsOptions = self.options.legend[legendType];


            if (!$.isArray(self.options.legend[legendType])) {
                legendsOptions = [self.options.legend[legendType]];
            }


            for (var j = 0; j < legendsOptions.length; ++j) {
                // Check for class existence
                if (legendsOptions[j].cssClass === "" || $("." + legendsOptions[j].cssClass, self.$container).length === 0) {
                    throw new Error("The legend class `" + legendsOptions[j].cssClass + "` doesn't exists.");
                }
                if (legendsOptions[j].display === true && $.isArray(legendsOptions[j].slices) && legendsOptions[j].slices.length > 0) {
                    self.drawLegend(legendsOptions[j], legendType, elems, scale, j);
                }
            }
        },


        /*
         * Set the attributes on hover and the attributes to restore for a map element
         * @param elem the map element
         * @param originalAttrs the original attributes to restore on mouseout event
         * @param attrsHover the attributes to set on mouseover event
         */
        setHoverOptions: function (elem, originalAttrs, attrsHover) {
            // Disable transform option on hover for VML (IE<9) because of several bugs
            if (Raphael.type != "SVG") delete attrsHover.transform;
            elem.attrsHover = attrsHover;


            if (elem.attrsHover.transform) elem.originalAttrs = $.extend({transform: "s1"}, originalAttrs);
            else elem.originalAttrs = originalAttrs;
        },


        /*
         * Set the hover behavior (mouseover & mouseout) for plots and areas
         * @param mapElem the map element
         * @param textElem the optional text element (within the map element)
         */
        setHover: function (mapElem, textElem) {
            var self = this;
            var $mapElem = {};
            var $textElem = {};
            var mouseoverTimeout = 0;
            var mouseoutTimeout = 0;
            var overBehaviour = function () {
                clearTimeout(mouseoutTimeout);
                mouseoverTimeout = setTimeout(function () {
                    self.elemHover(mapElem, textElem);
                }, 120);
            };
            var outBehaviour = function () {
                clearTimeout(mouseoverTimeout);
                mouseoutTimeout = setTimeout(function(){
                    self.elemOut(mapElem, textElem);
                }, 120);
            };


            $mapElem = $(mapElem.node);
            $mapElem.on("mouseover." + pluginName, overBehaviour);
            $mapElem.on("mouseout." + pluginName, outBehaviour);


            if (textElem) {
                $textElem = $(textElem.node);
                $textElem.on("mouseover." + pluginName, overBehaviour);
                $(textElem.node).on("mouseout." + pluginName, outBehaviour);
            }
        },


        /*
         * Remove the hover behavior for plots and areas
         * @param mapElem the map element
         * @param textElem the optional text element (within the map element)
         */
        unsetHover: function (mapElem, textElem) {
            $(mapElem.node).off("." + pluginName);
            if (textElem) $(textElem.node).off("." + pluginName);
        },


        /*
         * Set he behaviour for "mouseover" event
         * @param mapElem mapElem the map element
         * @param textElem the optional text element (within the map element)
         */
        elemHover: function (mapElem, textElem) {
            var self = this;
            // Set mapElem
            if (mapElem.attrsHover.animDuration > 0) mapElem.animate(mapElem.attrsHover, mapElem.attrsHover.animDuration);
            else mapElem.attr(mapElem.attrsHover);
            // Set textElem
            if (textElem) {
                if (textElem.attrsHover.animDuration > 0) textElem.animate(textElem.attrsHover, textElem.attrsHover.animDuration);
                else textElem.attr(textElem.attrsHover);
            }
            // workaround for older version of Raphael
            if (self.paper.safari) self.paper.safari();
        },


        /*
         * Set he behaviour for "mouseout" event
         * @param mapElem the map element
         * @param textElem the optional text element (within the map element)
         */
        elemOut: function (mapElem, textElem) {
            var self = this;
            // Set mapElem
            if (mapElem.attrsHover.animDuration > 0) mapElem.animate(mapElem.originalAttrs, mapElem.attrsHover.animDuration);
            else mapElem.attr(mapElem.originalAttrs);
            // Set textElem
            if (textElem) {
                if (textElem.attrsHover.animDuration > 0) textElem.animate(textElem.originalAttrs, textElem.attrsHover.animDuration);
                else textElem.attr(textElem.originalAttrs);
            }


            // workaround for older version of Raphael
            if (self.paper.safari) self.paper.safari();
        },


        /*
         * Get element options by merging default options, element options and legend options
         * @param defaultOptions
         * @param elemOptions
         * @param legendOptions
         */
        getElemOptions: function (defaultOptions, elemOptions, legendOptions) {
            var self = this;
            var options = $.extend(true, {}, defaultOptions, elemOptions);
            if (options.value !== undefined) {
                if ($.isArray(legendOptions)) {
                    for (var i = 0, length = legendOptions.length; i < length; ++i) {
                        options = $.extend(true, {}, options, self.getLegendSlice(options.value[i], legendOptions[i]));
                    }
                } else {
                    options = $.extend(true, {}, options, self.getLegendSlice(options.value, legendOptions));
                }
            }
            return options;
        },


        /*
         * Get the coordinates of the text relative to a bbox and a position
         * @param bbox the boundary box of the element
         * @param textPosition the wanted text position (inner, right, left, top or bottom)
         * @param margin number or object {x: val, y:val} margin between the bbox and the text
         */
        getTextPosition: function (bbox, textPosition, margin) {
            var textX = 0;
            var textY = 0;
            var textAnchor = "";


            if (typeof margin === "number") {
                if (textPosition === "bottom" || textPosition === "top") {
                    margin = {x: 0, y: margin};
                } else if (textPosition === "right" || textPosition === "left") {
                    margin = {x: margin, y: 0};
                } else {
                    margin = {x: 0, y: 0};
                }
            }


            switch (textPosition) {
                case "bottom" :
                    textX = ((bbox.x + bbox.x2) / 2) + margin.x;
                    textY = bbox.y2 + margin.y;
                    textAnchor = "middle";
                    break;
                case "top" :
                    textX = ((bbox.x + bbox.x2) / 2) + margin.x;
                    textY = bbox.y - margin.y;
                    textAnchor = "middle";
                    break;
                case "left" :
                    textX = bbox.x - margin.x;
                    textY = ((bbox.y + bbox.y2) / 2) + margin.y;
                    textAnchor = "end";
                    break;
                case "right" :
                    textX = bbox.x2 + margin.x;
                    textY = ((bbox.y + bbox.y2) / 2) + margin.y;
                    textAnchor = "start";
                    break;
                default : // "inner" position
                    textX = ((bbox.x + bbox.x2) / 2) + margin.x;
                    textY = ((bbox.y + bbox.y2) / 2) + margin.y;
                    textAnchor = "middle";
            }
            return {"x": textX, "y": textY, "textAnchor": textAnchor};
        },


        /*
         * Get the legend conf matching with the value
         * @param value the value to match with a slice in the legend
         * @param legend the legend params object
         * @return the legend slice matching with the value
         */
        getLegendSlice: function (value, legend) {
            for (var i = 0, length = legend.slices.length; i < length; ++i) {
                if ((legend.slices[i].sliceValue !== undefined && value == legend.slices[i].sliceValue)
                    || ((legend.slices[i].sliceValue === undefined)
                    && (legend.slices[i].min === undefined || value >= legend.slices[i].min)
                    && (legend.slices[i].max === undefined || value <= legend.slices[i].max))
                ) {
                    return legend.slices[i];
                }
            }
            return {};
        },


        /*
         * Animated view box changes
         * As from http://code.voidblossom.com/animating-viewbox-easing-formulas/,
         * (from https://github.com/theshaun works on mapael)
         * @param x coordinate of the point to focus on
         * @param y coordinate of the point to focus on
         * @param w map defined width
         * @param h map defined height
         * @param duration defined length of time for animation
         * @param easingFunction defined Raphael supported easing_formula to use
         * @param callback method when animated action is complete
         */
        animateViewBox: function (x, y, w, h, duration, easingFunction) {
            var self = this;
            var cx = self.paper._viewBox ? self.paper._viewBox[0] : 0;
            var dx = x - cx;
            var cy = self.paper._viewBox ? self.paper._viewBox[1] : 0;
            var dy = y - cy;
            var cw = self.paper._viewBox ? self.paper._viewBox[2] : self.paper.width;
            var dw = w - cw;
            var ch = self.paper._viewBox ? self.paper._viewBox[3] : self.paper.height;
            var dh = h - ch;
            var interval = 25;
            var steps = duration / interval;
            var currentStep = 0;
            var easingFormula;


            easingFunction = easingFunction || "linear";
            easingFormula = Raphael.easing_formulas[easingFunction];


            clearInterval(self.animationIntervalID);


            self.animationIntervalID = setInterval(function () {
                    var ratio = currentStep / steps;
                    self.paper.setViewBox(cx + dx * easingFormula(ratio),
                        cy + dy * easingFormula(ratio),
                        cw + dw * easingFormula(ratio),
                        ch + dh * easingFormula(ratio), false);
                    if (currentStep++ >= steps) {
                        clearInterval(self.animationIntervalID);
                        clearTimeout(self.zoomTO);
                        self.zoomTO = setTimeout(function () {
                            self.$map.trigger("afterZoom", {x1: x, y1: y, x2: (x + w), y2: (y + h)});
                        }, 150);
                    }
                }, interval
            );
        },


        /*
         * Check for Raphael bug regarding drawing while beeing hidden (under display:none)
         * See https://github.com/neveldo/jQuery-Mapael/issues/135
         * @return true/false
         *
         * Wants to override this behavior? Use prototype overriding:
         *     $.mapael.prototype.isRaphaelBBoxBugPresent = function() {return false;};
         */
        isRaphaelBBoxBugPresent: function(){
            var self = this;
            // Draw text, then get its boundaries
            var text_elem = self.paper.text(-50, -50, "TEST");
            var text_elem_bbox = text_elem.getBBox();
            // remove element
            text_elem.remove();
            // If it has no height and width, then the paper is hidden
            return (text_elem_bbox.width === 0 && text_elem_bbox.height === 0);
        },


        // Default map options
        defaultOptions: {
            map: {
                cssClass: "map",
                tooltip: {
                    cssClass: "mapTooltip"
                },
                defaultArea: {
                    attrs: {
                        fill: "black",
                        stroke: "red",
                        "stroke-width": 1,
                        "stroke-linejoin": "round"
                    },
                    attrsHover: {
                        fill: "#f38a03",
                        animDuration: 300
                    },
                    text: {
                        position: "inner",
                        margin: 10,
                        attrs: {
                            "font-size": 15,
                            fill: "#c7c7c7"
                        },
                        attrsHover: {
                            fill: "#eaeaea",
                            "animDuration": 300
                        }
                    },
                    target: "_self",
                    cssClass: "area"
                },
                defaultPlot: {
                    type: "circle",
                    size: 4+Math.round(Math.random()*4.5),
                    attrs: {
                        fill: "#CD5252",
                        stroke: "#fff",
                        "stroke-width": 0,
                        "stroke-linejoin": "round"
                    },
                    attrsHover: {
                        "stroke-width": 3,
                        animDuration: 300
                    },
                    text: {
                        position: "right",
                        margin: 10,
                        attrs: {
                            "font-size": 15,
                            fill: "#c7c7c7"
                        },
                        attrsHover: {
                            fill: "#eaeaea",
                            animDuration: 300
                        }
                    },
                    target: "_self",
                    cssClass: "plot"
                },
                defaultLink: {
                    factor: 0.5,
                    attrs: {
                        stroke: "#0088db",
                        "stroke-width": 2
                    },
                    attrsHover: {
                        animDuration: 300
                    },
                    text: {
                        position: "inner",
                        margin: 10,
                        attrs: {
                            "font-size": 15,
                            fill: "#c7c7c7"
                        },
                        attrsHover: {
                            fill: "#eaeaea",
                            animDuration: 300
                        }
                    },
                    target: "_self",
                    cssClass: "link"
                },
                zoom: {
                    enabled: false,
                    minLevel: 0,
                    maxLevel: 10,
                    step: 0.25,
                    mousewheel: true,
                    touch: true,
                    animDuration: 200,
                    animEasing: "linear",
                    buttons: {
                        "reset": {
                            cssClass: "zoomButton zoomReset",
                            content: "&#8226;", // bullet sign
                            title: "Reset zoom"
                        },
                        "in": {
                            cssClass: "zoomButton zoomIn",
                            content: "+",
                            title: "Zoom in"
                        },
                        "out": {
                            cssClass: "zoomButton zoomOut",
                            content: "&#8722;", // minus sign
                            title: "Zoom out"
                        }
                    }
                }
            },
            legend: {
                redrawOnResize: true,
                area: [],
                plot: []
            },
            areas: {},
            plots: {},
            links: {}
        },


        // Default legends option
        legendDefaultOptions: {
            area: {
                cssClass: "areaLegend",
                display: true,
                marginLeft: 10,
                marginLeftTitle: 5,
                marginBottomTitle: 10,
                marginLeftLabel: 10,
                marginBottom: 10,
                titleAttrs: {
                    "font-size": 16,
                    fill: "#343434",
                    "text-anchor": "start"
                },
                labelAttrs: {
                    "font-size": 12,
                    fill: "#343434",
                    "text-anchor": "start"
                },
                labelAttrsHover: {
                    fill: "#787878",
                    animDuration: 300
                },
                hideElemsOnClick: {
                    enabled: true,
                    opacity: 0.2,
                    animDuration: 300
                },
                slices: [],
                mode: "vertical"
            },
            plot: {
                cssClass: "plotLegend",
                display: true,
                marginLeft: 10,
                marginLeftTitle: 5,
                marginBottomTitle: 10,
                marginLeftLabel: 10,
                marginBottom: 10,
                titleAttrs: {
                    "font-size": 16,
                    fill: "#343434",
                    "text-anchor": "start"
                },
                labelAttrs: {
                    "font-size": 12,
                    fill: "#343434",
                    "text-anchor": "start"
                },
                labelAttrsHover: {
                    fill: "#787878",
                    animDuration: 300
                },
                hideElemsOnClick: {
                    enabled: true,
                    opacity: 0.2,
                    animDuration: 300
                },
                slices: [],
                mode: "vertical"
            }
        }


    };


    // Extend jQuery with Mapael
    if ($[pluginName] === undefined) $[pluginName] = Mapael;


    // Add jQuery DOM function
    $.fn[pluginName] = function (options) {
        // Call Mapael on each element
        return this.each(function () {
            // Avoid leaking problem on multiple instanciation by removing an old mapael object on a container
            if ($.data(this, pluginName)) {
                $.data(this, pluginName).destroy();
            }
            // Create Mapael and save it as jQuery data
            // This allow external access to Mapael using $(".mapcontainer").data("mapael")
            $.data(this, pluginName, new Mapael(this, options));
        });
    };


    return Mapael;


}));

(3)servlet部分:

package com.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;




import net.sf.json.JSONArray;
import net.sf.json.JSONObject;




public class TestServlet extends HttpServlet {


public void destroy() {
super.destroy(); 
}


public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}


public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
List<Orgin> orginlist = new ArrayList();
orginlist.add(new Orgin(10,"武漢"));
orginlist.add(new Orgin(8,"北京"));
orginlist.add(new Orgin(8,"上海"));
orginlist.add(new Orgin(7,"深圳"));
orginlist.add(new Orgin(6,"南京"));
orginlist.add(new Orgin(5,"西安"));
orginlist.add(new Orgin(5,"成都"));
orginlist.add(new Orgin(5,"杭州"));


List<Orgin> attackedlist = new ArrayList();
attackedlist.add(new Orgin(10,"福州"));
attackedlist.add(new Orgin(8,"兰州"));
attackedlist.add(new Orgin(8,"石家庄"));
attackedlist.add(new Orgin(7,"重庆"));
attackedlist.add(new Orgin(6,"安徽"));
attackedlist.add(new Orgin(5,"苏州"));
attackedlist.add(new Orgin(5,"郑州"));
attackedlist.add(new Orgin(5,"天津"));


List<AttackType> attacktypelist = new ArrayList();
attacktypelist.add(new AttackType(1, 8080, "smtp"));
attacktypelist.add(new AttackType(2, 603, "ftp"));
attacktypelist.add(new AttackType(3, 211, "dnfs"));
attacktypelist.add(new AttackType(4, 6030, "dos"));
attacktypelist.add(new AttackType(5, 755, "vdf"));
attacktypelist.add(new AttackType(6, 9009, "smtp"));
attacktypelist.add(new AttackType(7, 355, "smtp"));
attacktypelist.add(new AttackType(8, 707, "smtp"));

List<LiveAttack> liveattacklist =new ArrayList();
liveattacklist.add(new LiveAttack("08:30:20", "Fireball", "192.122.205.36", "192.136.35.30", "smtp", 3301,"boertu","rennes",180,"green"));
liveattacklist.add(new LiveAttack("08:30:21", "bluestar", "192.168.25.145", "192.26.135.33", "dos", 6404,"boertu1","boertu2",580,"yellow"));
liveattacklist.add(new LiveAttack("08:30:22", "waterdan", "192.168.25.136", "192.136.135.133", "vdf", 83,"city3","city2",180,"gray"));
/*liveattacklist.add(new LiveAttack("08:30:22", "waterdan", "192.168.25.136", "192.136.135.133", "vdf", 83,"北京","天津",180,"gray"));
liveattacklist.add(new LiveAttack("08:30:23", "hivv", "192.168.25.36", "192.225.35.11", "ftp", 9909,"北京","郑州",180,"blue"));
liveattacklist.add(new LiveAttack("08:30:30", "mizone", "192.168.25.36", "125.136.135.133", "dos",25,"杭州","上海",180,"gray"));
liveattacklist.add(new LiveAttack("08:31:20", "XSQ", "192.168.25.36", "192.136.35.43", "vdf", 80,"南京","上海",180,"gray"));
liveattacklist.add(new LiveAttack("08:32:20", "JASO", "192.168.25.36", "92.16.25.53", "smtp", 2331,"北京","包头",80,"green"));
liveattacklist.add(new LiveAttack("08:33:18", "Sky full", "192.168.25.36", "192.36.66.33", "smtp", 706,"北京","武汉",180,"green"));*/

JSONObject jsonObject = new JSONObject();
jsonObject.put("orginlist", orginlist);
jsonObject.put("attackedlist", attackedlist);
jsonObject.put("attacktypelist", attacktypelist);
jsonObject.put("liveattacklist", liveattacklist);
JSONArray jsonArray = new JSONArray();
jsonArray.add(jsonObject);

response.setContentType("text/html);charset=utf-8");
response.getWriter().write(jsonArray.toString());


}


public void init() throws ServletException {

}





}

  

原创粉丝点击