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
<!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>