text: var playground;
  
  function PlayGround(selector) {
    this.el = null;
    this.centerX = 400;
    this.centerY = 350;
    this.radius = 300;
    this.selector = selector;
  
    this.pairingData = [];
    this.playersData = [];
  
    this.connectionScale = d3.scaleLinear()
      .domain([0, 200])
      .range([0, 50]);
  
    this.init = function () {
      playground = this;
      this.el = d3.select(this.selector);
    };
  
    this.load = function (pairingData) {
      this.pairingData = pairingData;
      this.validPairsData = this.getValidPairs(pairingData);
      this.playersData = this.getPlayerNames(pairingData);
      this.clear();
      this.setGround();
      this.setPairing();
      this.setPlayers();
    };
  
    this.clear = function () {
      $(this.selector).children().remove();
    };
  
    this.setPairing = function () {
      this.el.selectAll(".connect")
        .data(playground.validPairsData)
        .enter().append("path")
        .attr("class", "connect")
        .attr("d", function (d) {
          var fromIndex = _.indexOf(playground.playersData, d[0]),
            fromCoordinates = playground.getPlayerCoordinates(fromIndex);
          var toIndex = (d[1] == "") ? fromIndex : _.indexOf(playground.playersData, d[1]),
            toCoordinates = playground.getPlayerCoordinates(toIndex);
          return "M " + fromCoordinates.x + " " + fromCoordinates.y + " Q 400 350 " +
            toCoordinates.x + " " + toCoordinates.y;
        })
        .attr("id", function (d) {
          return d.join('_')
        })
        .attr("stroke-width", function (d) {
          return playground.connectionScale(d[2])
        })
        .attr("data-from", function (d) {
          return d[0]
        })
        .attr("data-to", function (d) {
          return d[1]
        });
    };
  
    this.setGround = function () {
      this.el.append("circle")
        .attr("cx", this.centerX)
        .attr("cy", this.centerY)
        .attr("r", this.radius)
        .style("fill-opacity", 0.05);
    };
  
    this.setPlayers = function () {
      var colors = d3.scaleOrdinal(d3.schemeCategory20);
      var players = this.el.selectAll(".players")
        .data(playground.playersData)
        .enter()
        .append("g");
      players.append("circle")
        .attr("cx", function (d, i) {
          return playground.getPlayerCoordinates(i).x
        })
        .attr("cy", function (d, i) {
          return playground.getPlayerCoordinates(i).y
        })
        .attr("fill", colors)
        .attr("stroke-width", function (d) {
          return playground.connectionScale(playground.getSoloContribution(d))
        })
        .attr("class", "player")
        .attr("id", function (d) {
          return d
        })
        .on('mouseover', this.mouseOver)
        .on('mouseout', this.mouseOut);
      players.append("text")
        .attr("class", "player_names")
        .text(function (d) {
          return d
        })
        .attr("x", function (d, i) {
          return playground.getPlayerCoordinates(i).x + 2
        })
        .attr("y", function (d, i) {
          return playground.getPlayerCoordinates(i).y + 3
        })
        .attr("fill", "#000000");
    };
  
    this.getAllPairsContaining = function (name) {
      return _.filter(this.validPairsData, function (pair) {
        return pair.indexOf(name) >= 0
      });
    };
  
    this.toggleClass = function toggleClass(newClass, pair) {
      for (ii = 0; ii < 2; ii++) {
        d3.selectAll($("#" + pair[ii]))
          .attr("class", newClass)
      }
    };
  
    this.mouseOut = function (id) {
      showMatrix();
    };
  
    this.mouseOver = function (id) {
      var connectedPairs = _.unique(_.flatten(playground.getAllPairsContaining(id)));
      hideMatrix();
      if (connectedPairs.length == 0) {
        showPlayer(id);
        return;
      }
      
      connectedPairs.forEach(function (pair) {
        showPlayer(pair);
      });
      showPath(id);
    };
  
    this.updateConnectorsPath = function (playerId, newPoint) {
      playground.el
        .selectAll(".connect[data-from='" + playerId + "']")
        .attr("d", function (d) {
            return playground.replaceFromInPath($(this).attr("d"), newPoint);
          }
        );
      playground.el
        .selectAll(".connect[data-to='" + playerId + "']")
        .attr("d", function (d) {
            return playground.replaceToInPath($(this).attr("d"), newPoint);
          }
        );
    };
  
    this.getCollidingPlayer = function (player) {
      var svg = $("svg")[0];
      var rectangle = svg.createSVGRect();
      rectangle.x = player.attr("cx");
      rectangle.y = player.attr("cy");
      rectangle.width = player.attr("r");
      rectangle.height = player.attr("r");
      var allElements = svg.getIntersectionList(rectangle, null);
      return _.filter(allElements, function (elem) {
        return $(elem).attr("class") == "player" && $(elem).attr("id") != player.attr("id")
      });
    };
  
    this.replaceFromInPath = function (path, from) {
      var parts = path.split(" ");
      parts[1] = from[0];
      parts[2] = from[1];
      return parts.join(" ");
    };
  
    this.replaceToInPath = function (path, to) {
      var parts = path.split(" ");
      parts[6] = to[0];
      parts[7] = to[1];
      return parts.join(" ");
  
    };
  
    this.closestPointOnCircumference = function () {
      var vx = d3.event.x - this.centerX,
        vy = d3.event.y - this.centerY,
        magV = Math.sqrt(vx * vx + vy * vy);
      return [(this.centerX + vx / magV * this.radius), (this.centerY + vy / magV * this.radius)];
    };
  
    this.getPlayerCoordinates = function (index) {
      var distanceInDegrees = 2 * Math.PI / playground.playersData.length;
      return {
        x: (playground.radius * Math.sin(distanceInDegrees * index) + playground.centerX),
        y: (playground.radius * Math.cos(distanceInDegrees * index) + playground.centerY)
      }
    };
  
    this.getValidPairs = function (pairingData) {
      return _.filter(pairingData, function (data) {
        return (!_.isEmpty(data[0]) && !_.isEmpty(data[1])) && data.length == 3;
      })
    };
  
    this.getPlayerNames = function (pairingData) {
      var playerNames = [];
      _.each(pairingData, function (data) {
        playerNames.push(data[0]);
        playerNames.push(data[1]);
      });
      playerNames = _.compact(playerNames);
      return _.sortBy(_.uniq(playerNames), function (d) {
        return d
      });
    };
  
    this.getSoloContribution = function (name) {
      var contrib = _.find(this.pairingData, function (data) {
        return ((data[0] == name && _.isEmpty(data[1])) || (data[1] == name && _.isEmpty(data[0])));
      });
      return contrib ? contrib[2] : 1;
    };
  
    var showPlayer = function (player) {
      $("#" + player).show();
      $("#" + player).siblings().show();
    };
  
    var hideMatrix = function () {
      $('circle').hide();
      $('g.area>circle').show();
      $('path').hide();
      $('text').hide();
    };
  
    var showMatrix = function () {
      $('circle').show();
      $('path').show();
      $('text').show();
    };
  
    var showPath = function (player) {
      $("path[data-from='" + player + "']").show();
      $("path[data-to='" + player + "']").show();
    };
  
    this.init();
  }
  