block by micahstubbs 839562c1f94fcc175fe0af9d5dccf037

Foreign players connected to Chinese Super League teams V2

Full Screen

How are professional soccer players from the top 32 countries qualified for the 2018 FIFA World Cup involved in the Chinese Super League?

Hover over any team node to see how players from each country are connected to teams

Built with blockbuilder.org

forked from dianaow‘s block: Foreign players connected to Chinese Super League teams V2

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
  </style>
</head>

<body>
  <script>
d3.csv("csl_foreign_players.csv", function(csv) {

  var player, circle, path, text
  var radius = 320
  var margin = {top: 20, right: 20, bottom: 20, left: 20}
  var width = 1000 - margin.left - margin.right
  var height = 700 - margin.top - margin.bottom

  var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var data = csv.map(function(d) {
    return {
      country: d.Country,
      player: d.Player,
      star: d.Star_Player,
      club: d.Club_Name_1 + " " + d.Club_Name_2
    }
  })

  //var countries = ['Germany', 'Brazil', 'Belgium', 'Portugal', 'Argentina', 'Switzerland','France', 'Poland', 'Spain', 'Peru', 'Denmark', 'England', 'Uruguay', 'Colombia', 'Croatia', 'Tunisia', 'Iceland', 'Costa Rica', 'Sweden', 'Senegal', 'Serbia', 'Australia', 'Iran', 'Morocco', 'Egypt', 'Nigeria', 'South Korea', 'Japan']
  var countries = ['Germany', 'Brazil', 'Belgium', 'Portugal', 'England', 'Poland','France', 'Argentina', 'Spain', 'Peru', 'South Korea', 'Switzerland', 'Uruguay', 'Colombia', 'Croatia', 'Costa Rica', 'Nigeria', 'Iceland', 'Sweden', 'Serbia', 'Senegal', 'Iran', 'Australia', 'Morocco', 'Egypt', 'Tunisia', 'Denmark', 'Japan']

  var country_stats  = d3.nest()
    .key(function(d) { return d.country }).sortKeys(function(a,b) { return countries.indexOf(a) - countries.indexOf(b); })
    .entries(data);

  var countryNodes = []
  country_stats.map(function(d,i) {
      var radian = (2 * Math.PI) / countries.length * i - (Math.PI / 2);
      countryNodes.push({
        id: d.key,
        size: 6,
        fill: 'none', // standardize empty black circle for each team
        stroke: 'black',
        type: 'country',
        fx: radius * Math.cos(radian) + (width / 2),
        fy: radius * Math.sin(radian) + (height / 2)
      })
  })

  var team_stats = d3.nest()
    .key(function(d) { return d.club })
    .key(function(d) { return d.country })
    .rollup(function(leaves) { return leaves.length })
    .entries(data)

  var countryNodes_nested = d3.nest()
    .key(function(d) { return d.id })
    .entries(countryNodes)

  var teamNodes = []
  var links = []
  team_stats.map(function(d,i) {
    var node = {
      id: d.key,
      size: d.values.length*2,
      fill: '#F4B95F', // standardize empty black circle for each team
      stroke: '#F4B95F',
      type: 'team',
      x: width/2,
      y: height/2
    }
    teamNodes.push(node)
  })
  
  var team_stats1 = d3.nest()
    .key(function(d) { return d.country })
    .entries(data)

  var linkStrengths = []
  team_stats1.map(function(d,i) {
    linkStrengths.push(d.values.length)
  })

  var strengthScale = d3.scaleLinear()
    .domain([d3.min(linkStrengths), d3.max(linkStrengths)])
    .range([0, 1])

  var playerNodes = []
  var playerLinks = []
  team_stats1.map(function(d,i) {
    var radian = (2 * Math.PI) / countries.length * i - (Math.PI / 2);
    d.values.map(x => {
      var node = {
        text: x.player,
        id: x.player.replace(/[^A-Z0-9]+/ig, "_") + "/" + x.club.replace(/[^A-Z0-9]+/ig, "_"),
        size: 2,
        fill: 'grey',
        stroke: 'none',
        type: 'player',
        x: radius-50 * Math.cos(radian) + (width / 2),
        y: radius-50 * Math.sin(radian) + (height / 2)
      }
      playerNodes.push(node)
      links.push({
        source: countryNodes_nested.find(n=>n.key == x.country).values[0],
        target: node,
        size: 0.5,
        strength: 0.6,
        stroke: 'grey',
        type: 'country_player',
        // remove spaces because they cannot be contained in class/id names
        id: x.country.replace(/[^A-Z0-9]+/ig, "_") + "/" + x.player.replace(/[^A-Z0-9]+/ig, "_") + x.club.replace(/[^A-Z0-9]+/ig, "_")
      })
      links.push({
        source: node,
        target: teamNodes.find(n=>n.id == x.club),
        size: 0.5,
        strength: strengthScale(d.values.length),
        stroke: '#F4B95F',
        type: 'player_team',
        id: x.player.replace(/[^A-Z0-9]+/ig, "_") + "/" + x.club.replace(/[^A-Z0-9]+/ig, "_")
      })
    })
  })

  var nodes = countryNodes.concat(teamNodes)
  nodes = nodes.concat(playerNodes)
  console.log(nodes)
  enter()

  // Initialize force simulation
  var simulation = d3.forceSimulation()
    .force("link", d3.forceLink()
      .id(function(d) { return d.id; })
      .strength(function(d) {return d.strength})
      .distance(40)
    )
    .force("collide", d3.forceCollide().radius(function(d) { return d.size * 1.3 }))
    //.force("collide", d3.forceCollide().radius(function(d) { return d.type == 'player' ?  d.size * 1.8 : d.size * 1.3 }))


  simulation
      .nodes(nodes)
      .on("tick", update)

  simulation.force("link")
      .links(links)

  function enter() {

    path = svg.selectAll('line')
      .data(links).enter().append('line')
      .attr('stroke-linecap', 'round')

    circle = svg.selectAll('circle')
      .data(nodes)
      .enter().append('circle')
      .attr('stroke-width', 2)

    text_country = svg.selectAll('.text_country')
      .data(nodes.filter(d=>d.type=='country'))
      .enter().append('text')
      .attr('class', 'text_country')
      .attr('text-anchor', 'middle')
      .attr('dy', '.35em')
      .attr('fill', '#555')
      .style('font-size', '12px')
      .style('font-family', 'Helvetica')
      .style('font-weight', 'bold')
      .style('pointer-events', 'none')

    text_team = svg.selectAll('text_team')
      .data(nodes.filter(d=>(d.type=='team') & (d.size>10)))
      .enter().append('text')
      .attr('class', 'text_text')
      .attr('text-anchor', 'middle')
      .attr('dy', '.35em')
      .attr('fill', '#555')
      .style('font-size', '12px')
      .style('font-family', 'Helvetica')
      .style('font-weight', 'normal')
      .style('pointer-events', 'none')

    text_player = svg.selectAll('text_player')
      .data(nodes.filter(d=>d.type=='player'))
      .enter().append('text')
      .attr('class', 'text_player')
      .attr('text-anchor', 'middle')
      .attr('dy', '.35em')
      .attr('fill', '#555')
      .style('font-size', '10px')
      .style('font-family', 'Helvetica')
      .style('font-weight', 'normal')
      .style('pointer-events', 'none')
      .style('opacity', 0)

    text1 = text_team.append('tspan')
    text2 = text_team.append('tspan')

  }

  function update() {

    path.attr('stroke-width', function(d) {return d.size})
      .attr('stroke', function(d) {return d.stroke})
      .attr('fill', function(d) {return d.stroke})
      .attr('x1', function(d) {return d.source.x})
      .attr('y1', function(d) {return d.source.y})
      .attr('x2', function(d) {return d.target.x})
      .attr('y2', function(d) {return d.target.y})
      .attr('class', function(d) {return d.type})
      .attr('id', function(d) {return d.id})

    circle.attr('r', function(d) {return d.size})
      .attr('fill', function(d) {return d.fill || '#fff'})
      .attr('stroke', function(d) {return d.stroke || 'none'})
      .attr('cx', function(d) {return d.x})
      .attr('cy', function(d) {return d.y})
      .attr('class', function(d) {return d.type})
      .attr('id', function(d) {return d.id}) 

    text_country.attr('x', function(d) {return d.x})
      .attr('y', function(d) {return d.y + 15})
      .attr('id', function(d) {return d.id}) 
      .text(function(d) {return d.id})
      
    text1.attr('x', function(d) {return d.x})
      .attr('y', function(d) {return d.y-8})
      .attr('id', function(d) {return d.id.replace(/[^A-Z0-9]+/ig, "_")}) 
      .text(function(d) {return d.id.split(' ')[0]})  

    text2.attr('x', function(d) {return d.x})
      .attr('y', function(d) {return d.y+8})
      .attr('id', function(d) {return d.id.replace(/[^A-Z0-9]+/ig, "_")}) 
      .text(function(d) {return d.id.split(' ')[1]})  

    text_player.attr('x', function(d) {return d.x})
      .attr('y', function(d) {return d.y})
      .attr('id', function(d) {return d.id}) 
      .text(function(d) {return d.text})
  
    interactive()
  }

  function interactive() {
    d3.selectAll('.team').on('mouseover', function (l) {

      d3.selectAll(".country_player").style('opacity', 0) // make all country-player links invisible
      d3.selectAll(".player_team").style('opacity', 0) // make all player-team links invisible
      d3.selectAll('.team').style('opacity', 0) // make all team nodes invisible
      d3.selectAll('.player').style('opacity', 0) // make all player nodes invisible
      
      text1.style('opacity', 0) // make all team node labels invisible
      text2.style('opacity', 0) // make all team node labels invisible

      // only select links and nodes connected to specific team node hovered upon visible
      d3.selectAll("line[id*='" + l.id.replace(/[^A-Z0-9]+/ig, "_") + "']")
        .each(function(d,i) {
          var player = d3.select(this).attr('id')
          d3.selectAll("line[id*='" + player + "']")
            .style('opacity', 1)
          d3.selectAll("text[id*='" + player + "']")
            .style('opacity', 1)
          d3.selectAll("circle[id*='" + player + "']")
            .style('opacity', 1)
        })

      // only select node labels for the specific team node hovered upon visible
      d3.selectAll("#" + l.id.replace(/[^A-Z0-9]+/ig, "_")).style('opacity', 1)
      d3.selectAll("circle[id*='" + l.id + "']").style('opacity', 1)

    })
    .on('mouseout', function (l) {

      d3.selectAll(".country_player").style('opacity', 1) 
      d3.selectAll(".player_team").style('opacity', 1) 
      d3.selectAll('.team').style('opacity', 1) 
      d3.selectAll('.player').style('opacity', 1) 
      
      text1.style('opacity', 1) 
      text2.style('opacity', 1) 
      text_player.style('opacity', 0) 

    })
  }

})
  </script>
</body>