<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
        .country {
            fill: lightgray;
            stroke: darkgrey;
            stroke-width: 1px;
        } 

        .country:hover {
            fill: darkgrey;
        }

        div.tooltipDestination {
          position: absolute;
          text-align: center;
          width: 60px;
          height: 16px;
          padding: 2px;
          font: 12px sans-serif;
          background: red;
          border: 0px;
          border-radius: 0px;
          pointer-events: none;
          padding-top: 5px;
        }

        div.tooltipOrigin {
          position: absolute;
          text-align: center;
          width: 60px;
          height: 16px;
          padding: 2px;
          font: 12px sans-serif;
          background: green;
          border: 0px;
          border-radius: 0px;
          pointer-events: none;
          padding-top: 5px;
        }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script src="http://d3js.org/d3.v4.min.js"></script>
    <script src="http://d3js.org/topojson.v1.min.js"></script>
    <script>
      var originGeo = [16.8286, 52.4200];
      var originName = 'POZ';
      var destinations = [
          {'coord': [20.9679, 52.1672], 'name': 'WAW'},
          {'coord': [23.9569, 49.8134], 'name': 'LWO'},
          {'coord': [30.4433, 50.4120], 'name': 'IEV'},
          {'coord': [13.3724, 55.5355], 'name': 'MMX'},
          {'coord': [12.6508, 55.6180], 'name': 'CPH'},
          {'coord': [16.9154, 58.7890], 'name': 'NYO'},
          {'coord': [10.2569, 59.1824], 'name': 'TRF'},
          {'coord': [9.1526, 55.7408], 'name': 'BLL'},
          {'coord': [8.5622, 50.0379], 'name': 'FRA'},
          {'coord': [11.7750, 48.3537], 'name': 'MUC'},
          {'coord': [5.3921, 51.4584], 'name': 'EIN'},
          {'coord': [2.1115, 49.4545], 'name': 'BVA'},
          {'coord': [-2.7135, 51.3836], 'name': 'BRS'},
          {'coord': [0.3717, 51.8763], 'name': 'LTN'},
          {'coord': [0.2389, 51.8860], 'name': 'STN'},
          {'coord': [-1.743507, 52.4524], 'name': 'BHX'},
          {'coord': [-2.8544, 53.3375], 'name': 'LPL'},
          {'coord': [-3.3615, 55.9508], 'name': 'EDI'},
          {'coord': [-1.010464, 53.480662], 'name': 'DSA'},
          {'coord': [-6.2499, 53.4264], 'name': 'DUB'},
          {'coord': [-0.560056, 38.285483], 'name': 'ALC'}, 
          {'coord': [0.065603, 40.207479], 'name': 'CDT'},
          {'coord': [-3.56795, 40.4839361], 'name': 'MAD'},
          {'coord': [2.071062, 41.288288], 'name': 'BCN'},
          {'coord': [2.766066, 41.898201], 'name': 'GRO'},
          {'coord': [14.483279, 35.854114], 'name': 'MLA'},     
          {'coord': [23.9484, 37.9356467], 'name': 'ATH'},   
          {'coord': [19.914486, 39.607645], 'name': 'CFU'},
          {'coord': [34.9362, 29.9511], 'name': 'VDA'},
          {'coord': [34.8854, 32.0055], 'name': 'TLV'}
      ];
      var svg;
      var projection;
      var speed = 2800;//km/sec
      var tooltip = d3.select('#map').append('div')	
          .attr('class', 'tooltipDestination')				
          .style('opacity', 0);

      function getArc(d, s) {
        var dx = d.destination.x - d.origin.x;
        var dy = d.destination.y - d.origin.y;
        var dr = Math.sqrt(dx * dx + dy * dy);
        var spath = s == false ? ' 0 0,0 ' : ' 0 0,1 ';
        return 'M' + d.origin.x + ',' + d.origin.y + 'A' + dr + ',' + dr + spath + d.destination.x + ',' + d.destination.y;
      }

      function calculateDistance(lat1, lon1, lat2, lon2) {
        var p = 0.017453292519943295;
        var c = Math.cos;
        var a = 0.5 - c((lat2 - lat1) * p)/2 + c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))/2;
        return 12742 * Math.asin(Math.sqrt(a));
      }

      function calculateDuration(distance) {
        return (distance / this.speed) * 1000;
      }

      function drawConnection(index) {
        var destination = this.destinations[index];
        var originPos = projection(this.originGeo);
        var destinationPos = projection(destination.coord);
        var connection = [ originPos, destinationPos ];
        var destinationName = destination.name;
        var originGeo = this.originGeo;
        var destinationGeo = destination.coord;
        var svg = this.svg;
        var distance = calculateDistance(originGeo[1], originGeo[0], destinationGeo[1], destinationGeo[0]);
        var duration = calculateDuration(distance);
        var arc = svg
          .append('path')
          .datum(connection)
          .attr('class', 'arc' + index)
          .attr('d', function(coordinates) {
              var d = {
                  origin: { x: coordinates[0][0], y: coordinates[0][1]},
                  destination: { x: coordinates[1][0], y: coordinates[1][1]}
              };
              var s = false;
              if (d.destination.x > d.origin.x) {
                  s = true;
              }
              return getArc(d, s);
          }) 
          .style('stroke', 'steelblue')
          .style('stroke-width', 2)
          .style('fill', 'none')
          .transition()
          .duration(duration)
          .attrTween('stroke-dasharray', function() {
              var len = this.getTotalLength();
              return function(t) {
                return (d3.interpolate('0,' + len, len + ',0'))(t)
              };
          })
          .on('end', function(d) {
              var c = connection[1];
              svg.append('circle')
                .attr('cx', c[0])
                .attr('cy', c[1])
                .attr('r', 0)
                .attr('class', 'destCircleInner')
                .style('fill', 'steelblue')
                .style('fill-opacity', '1')
                .transition()
                .duration(300)
                .attr('r', '3px');
              svg.append('circle')
                .attr('cx', c[0])
                .attr('cy', c[1])
                .attr('r', 0)
                .attr('class', 'destCircleOuter')
                .style('fill', 'black')
                .style('fill-opacity', '0.05')
                .transition()
                .duration(300)
                .attr('r', '10px');
              svg.append('circle')
                .datum(c)
                .attr('cx', c[0])
                .attr('cy', c[1])
                .attr('r', 0)
                .style('class', 'destCircleMouse')
                .style('fill', 'steelblue')
                .style('fill-opacity', '1')
                .on('mouseover', function (d) {
                  tooltip.html('<span style="color:white">' + destinationName + '</span>')
                    .attr('class', 'tooltipDestination')
                    .style('left', d[0] + 12 + 'px')
                    .style('top', d[1] - 20 + 'px')
                    .transition()
                    .duration(700)
                    .style('opacity', 1)
                }) 
                .on('mouseout', function (d) {
                  tooltip.transition()
                  .duration(700)
                  .style('opacity', 0)
                })
                .transition()
                .duration(300)
                .attr('r', '3px')
                .on('end', function(d) {
                  d3.select(this)
                    .transition()
                    .duration(2000)
                    .attr('r', 20)
                    .style('fill-opacity', '0');
                  d3.select('.arc' + index)
                    .transition()
                    .duration(2000)
                    .style('stroke-opacity', '0')
                    .style('stroke-width', '1')
                    .on('end', function (d) {
                      if (index === destinations.length - 1) {
                        svg.selectAll('.destCircleInner').remove();
                        svg.selectAll('.destCircleOuter').remove();
                        svg.selectAll('.destCircleMouse').remove();
                        for (i = 0; i < destinations.length; i++) { 
                          svg.selectAll('.arc' + i).remove();
                        }
                      }
                      var nextIndex = index + 1;
                      if (nextIndex < destinations.length) {
                        drawConnection(nextIndex);
                      } else {
                        drawConnection(0);
                      }
                    })
                });
          });
      }

      function drawConnections() {
        drawConnection(0);
      }
      
      function drawMap(originName, originGeo, destinations) {
        var countries, height, path, projection, scale, svg, width;
        var width = 800;
        var height = 800;
        var center = [4, 68.6];
        var scale = 700;
        projection = d3.geoMercator().scale(scale).translate([width / 2, 0]).center(center);
        path = d3.geoPath().projection(projection);
        svg = d3.select('#map').append('svg')
          .attr('height', height)
          .attr('width', width)
          .style('background', '#C1E1EC');
        countries = svg.append("g");
        d3.json('europe.json', function(data) {
          countries.selectAll('.country')
          .data(topojson.feature(data, data.objects.europe).features)
          .enter()
          .append('path')
          .attr('class', 'country')
          .attr('d', path)  
          return;
        });
        var source = svg.selectAll('circleOrigin');
          source
          .data([originGeo]).enter()
          .append('circle')
          .attr('cx', function (d) { return projection(d)[0]; })
          .attr('cy', function (d) { return projection(d)[1]; })
          .attr('r', '3px')
          .style('opacity', 1)
          .attr('fill', 'green')
          .attr('class', 'circleOrigin')
          source
          .data([originGeo]).enter()
          .append('circle')
          .attr('cx', function (d) { return projection(d)[0]; })
          .attr('cy', function (d) { return projection(d)[1]; })
          .attr('r', '10px')
          .style('opacity', 0.05)
          .attr('fill', 'black')
          .attr('class', 'circleOrigin')
          .on('mouseover', function (d) {
            tooltip.html('<span style="color:white">' + originName + '</span>')
              .attr('class', 'tooltipOrigin')
              .style('left', projection(d)[0] + 12 + 'px')
              .style('top', projection(d)[1] - 20 + 'px')
              .transition()
              .duration(700)
              .style('opacity', 1)
          })
          .on('mouseout', function (d) {
            tooltip.transition()
              .duration(700)
              .style('opacity', 0)
          });
        this.svg = svg;
        this.projection = projection;
        this.drawConnections();
      };
      this.drawMap(this.originName, this.originGeo, this.destinations);
    </script>
  </body>
</html>