Built with blockbuilder.org
Created
November 24, 2017 16:04
-
-
Save basilesimon/7802f6498ca5904c92041312876ace3e to your computer and use it in GitHub Desktop.
Treemap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "transfers_position_in", | |
"children": [ | |
{ | |
"name": "Defender", | |
"children": [ | |
{ | |
"name": "Cohen Bramall", | |
"fee": "0.04", | |
"fromto": "Hednesford Town to Arsenal", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Cohen Bramall" | |
}, | |
{ | |
"name": "Robbie Brady", | |
"fee": "13.00", | |
"fromto": "Norwich City to Burnley", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Robbie Brady" | |
}, | |
{ | |
"name": "Jeffrey Schlupp", | |
"fee": "12.50", | |
"fromto": "Leicester City to Crystal Palace", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Jeffrey Schlupp" | |
}, | |
{ | |
"name": "Patrick v. Aanholt", | |
"fee": "14.00", | |
"fromto": "Sunderland to Crystal Palace", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Patrick v. Aanholt" | |
}, | |
{ | |
"name": "Mamadou Sakho", | |
"fee": "0.00", | |
"fromto": "Liverpool to Crystal Palace", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Mamadou Sakho" | |
}, | |
{ | |
"name": "Omar Elabdellaoui", | |
"fee": "0.00", | |
"fromto": "Olympiacos to Hull City", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Omar Elabdellaoui" | |
}, | |
{ | |
"name": "Andrea Ranocchia", | |
"fee": "0.00", | |
"fromto": "Inter Milan to Hull City", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Andrea Ranocchia" | |
}, | |
{ | |
"name": "Molla Wague", | |
"fee": "0.00", | |
"fromto": "Granada CF to Leicester City", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Molla Wague" | |
}, | |
{ | |
"name": "Joleon Lescott", | |
"fee": "0.00", | |
"fromto": "AEK Athens to Sunderland", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Joleon Lescott" | |
}, | |
{ | |
"name": "Bryan Oviedo", | |
"fee": "3.75", | |
"fromto": "Everton to Sunderland", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Bryan Oviedo" | |
}, | |
{ | |
"name": "Martin Olsson", | |
"fee": "4.50", | |
"fromto": "Norwich City to Swansea City", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Martin Olsson" | |
}, | |
{ | |
"name": "Marc Wilson", | |
"fee": "0.00", | |
"fromto": "Bournemouth to West Bromwich Albion", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Marc Wilson" | |
}, | |
{ | |
"name": "José Fonte", | |
"fee": "8.00", | |
"fromto": "Southampton to West Ham United", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.José Fonte" | |
}, | |
{ | |
"name": "Nathan Holland", | |
"fee": "0.00", | |
"fromto": "Everton to West Ham United", | |
"position": "Defender", | |
"id": "transfers_position_in.Defender.Nathan Holland" | |
} | |
], | |
"id": "transfers_position_in.Defender" | |
}, | |
{ | |
"name": "Goalkeeper", | |
"children": [ | |
{ | |
"name": "Aaron Ramsdale", | |
"fee": "0.04", | |
"fromto": "Sheffield United to Bournemouth", | |
"position": "Goalkeeper", | |
"id": "transfers_position_in.Goalkeeper.Aaron Ramsdale" | |
}, | |
{ | |
"name": "Mouez Hassen", | |
"fee": "0.00", | |
"fromto": "OGC Nice to Southampton", | |
"position": "Goalkeeper", | |
"id": "transfers_position_in.Goalkeeper.Mouez Hassen" | |
}, | |
{ | |
"name": "Lee Grant", | |
"fee": "1.30", | |
"fromto": "Derby County to Stoke City", | |
"position": "Goalkeeper", | |
"id": "transfers_position_in.Goalkeeper.Lee Grant" | |
} | |
], | |
"id": "transfers_position_in.Goalkeeper" | |
}, | |
{ | |
"name": "Midfielder", | |
"children": [ | |
{ | |
"name": "Joey Barton", | |
"fee": "0.00", | |
"fromto": "Rangers to Burnley", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Joey Barton" | |
}, | |
{ | |
"name": "Ashley Westwood", | |
"fee": "5.00", | |
"fromto": "Aston Villa to Burnley", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Ashley Westwood" | |
}, | |
{ | |
"name": "Luka Milivojevic", | |
"fee": "13.60", | |
"fromto": "Olympiacos to Crystal Palace", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Luka Milivojevic" | |
}, | |
{ | |
"name": "Morgan Schneiderlin", | |
"fee": "24.00", | |
"fromto": "Manchester United to Everton", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Morgan Schneiderlin" | |
}, | |
{ | |
"name": "Evandro Goebel", | |
"fee": "2.13", | |
"fromto": "Porto to Hull City", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Evandro Goebel" | |
}, | |
{ | |
"name": "Markus Henriksen", | |
"fee": "4.25", | |
"fromto": "AZ Alkmaar to Hull City", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Markus Henriksen" | |
}, | |
{ | |
"name": "Lazar Markovic", | |
"fee": "0.00", | |
"fromto": "Liverpool to Hull City", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Lazar Markovic" | |
}, | |
{ | |
"name": "Alfred N'Diaye", | |
"fee": "0.00", | |
"fromto": "Villarreal to Hull City", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Alfred N'Diaye" | |
}, | |
{ | |
"name": "Wilfred Ndidi", | |
"fee": "15.00", | |
"fromto": "Genk to Leicester City", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Wilfred Ndidi" | |
}, | |
{ | |
"name": "Adlene Guedioura", | |
"fee": "0.00", | |
"fromto": "Watford to Middlesbrough", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Adlene Guedioura" | |
}, | |
{ | |
"name": "Darron Gibson", | |
"fee": "3.75", | |
"fromto": "Everton to Sunderland", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Darron Gibson" | |
}, | |
{ | |
"name": "Luciano Narsingh", | |
"fee": "4.00", | |
"fromto": "PSV Eindhoven to Swansea City", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Luciano Narsingh" | |
}, | |
{ | |
"name": "Tom Carroll", | |
"fee": "4.50", | |
"fromto": "Tottenham Hotspur to Swansea City", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Tom Carroll" | |
}, | |
{ | |
"name": "Tom Cleverley", | |
"fee": "0.00", | |
"fromto": "Everton to Watford", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Tom Cleverley" | |
}, | |
{ | |
"name": "Jake Livermore", | |
"fee": "10.00", | |
"fromto": "Hull City to West Bromwich Albion", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Jake Livermore" | |
}, | |
{ | |
"name": "Robert Snodgrass", | |
"fee": "10.20", | |
"fromto": "Hull City to West Ham United", | |
"position": "Midfielder", | |
"id": "transfers_position_in.Midfielder.Robert Snodgrass" | |
} | |
], | |
"id": "transfers_position_in.Midfielder" | |
}, | |
{ | |
"name": "Striker", | |
"children": [ | |
{ | |
"name": "Ademola Lookman", | |
"fee": "7.50", | |
"fromto": "Charlton Athletic to Everton", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Ademola Lookman" | |
}, | |
{ | |
"name": "Oumar Niasse", | |
"fee": "0.00", | |
"fromto": "Everton to Hull City", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Oumar Niasse" | |
}, | |
{ | |
"name": "Kamil Grosicki", | |
"fee": "7.00", | |
"fromto": "Stade Rennais to Hull City", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Kamil Grosicki" | |
}, | |
{ | |
"name": "Gabriel Jesus", | |
"fee": "27.20", | |
"fromto": "Palmeiras to Manchester City", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Gabriel Jesus" | |
}, | |
{ | |
"name": "Rudy Gestede", | |
"fee": "6.00", | |
"fromto": "Aston Villa to Middlesbrough", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Rudy Gestede" | |
}, | |
{ | |
"name": "Patrick Bamford", | |
"fee": "10.00", | |
"fromto": "Chelsea to Middlesbrough", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Patrick Bamford" | |
}, | |
{ | |
"name": "Manolo Gabbiadini", | |
"fee": "14.45", | |
"fromto": "Napoli to Southampton", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Manolo Gabbiadini" | |
}, | |
{ | |
"name": "Saido Berahino", | |
"fee": "15.00", | |
"fromto": "West Bromwich Albion to Stoke City", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Saido Berahino" | |
}, | |
{ | |
"name": "Jordan Ayew", | |
"fee": "0.00", | |
"fromto": "Aston Villa to Swansea City", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Jordan Ayew" | |
}, | |
{ | |
"name": "Mauro Zárate", | |
"fee": "2.30", | |
"fromto": "Fiorentina to Watford", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.Mauro Zárate" | |
}, | |
{ | |
"name": "M'Baye Niang", | |
"fee": "0.00", | |
"fromto": "AC Milan to Watford", | |
"position": "Striker", | |
"id": "transfers_position_in.Striker.M'Baye Niang" | |
} | |
], | |
"id": "transfers_position_in.Striker" | |
} | |
], | |
"id": "transfers_position_in" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://unpkg.com/d3"></script> | |
<script src="https://unpkg.com/d3-jetpack"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
svg { | |
margin: 0 auto; | |
display: flex; | |
background-color: #f8f7f1; | |
} | |
text, .text { | |
font: 15px arial; | |
fill: white; | |
color: white; | |
} | |
.d3-tip { | |
line-height: 1; | |
font: 15px arial; | |
fill: white; | |
color: white; | |
z-index: 1000; | |
} | |
.legendTitle text { | |
font: 18px Times New Roman; | |
fill: black; | |
} | |
.playerInfoTitle text { | |
font-size: 30px; | |
font-family: Times New Roman; | |
fill: black; | |
} | |
.playerInfo text { | |
font: 15px arial; | |
fill: #696969; | |
&.mobile { | |
font: 20px serif; | |
fill: #1d1d1d; | |
} | |
} | |
.playerNamesFee { | |
font-size: 15px; | |
fill: rgba(255,255,255, .6) | |
} | |
</style> | |
</head> | |
<body> | |
<svg id="chart"></svg> | |
<script> | |
// set config object | |
const config = { width: 700, height: 450 }; | |
const margin = { top: 20, left: 20, right: 150 }; | |
const width = config.width - margin.left - margin.right, | |
height = config.height - margin.top; | |
const svg = d3.select('#chart').at({ | |
width: config.width, | |
height: config.height, | |
}); | |
// construct an ordinal scale from our colour palette | |
const timesColors = ['#254251', '#E0AB26', '#F37F2F', '#3292A6', '#6c3c5e']; | |
const color = d3.scaleOrdinal(timesColors); | |
const format = d3.format(',d'); | |
d3.json('data.json', (err, dataset) => { | |
if (err) { | |
console.log(err); | |
return; | |
} | |
const treemap = d3 | |
.treemap() | |
.tile(d3.treemapResquarify) | |
.size([width, height]) | |
.round(true) | |
.paddingOuter(2) | |
.paddingInner(1); | |
const root = d3 | |
.hierarchy(dataset) | |
.eachBefore( | |
d => (d.data.id = (d.parent ? d.parent.data.id + '.' : '') + d.data.name) | |
) | |
.sum(sumBySize) | |
.sort((a, b) => b.height - a.height || b.value - a.value); | |
treemap(root); | |
// One cell per player | |
const container = svg.append('g').at({ class: 'container' }); | |
const cell = container | |
.selectAll('g') | |
.data(root.leaves()) | |
.enter() | |
.append('g') | |
.translate(d => [d.x0, d.y0]); | |
cell | |
.append('rect') | |
.at({ | |
id: d => d.data.id, | |
class: d => (d.x1 - d.x0 > 120 && d.y1 - d.y0 > 40 ? 'wide' : null), | |
width: d => d.x1 - d.x0, | |
height: d => d.y1 - d.y0, | |
fill: d => color(d.parent.data.id), | |
}) | |
.on('mouseover', function(d) { | |
var _this = this; | |
d3 | |
.selectAll('rect') | |
.transition() | |
.duration(100) | |
.style('opacity', function() { | |
return this === _this ? 1.0 : 0.6; | |
}); | |
}) | |
.on('mouseout', function(d) { | |
d3.selectAll('rect').transition().duration(500).style('opacity', 1); | |
}) | |
.on('click', function(d) { | |
appendPlayerInfo(this, d); | |
}); | |
// Player names | |
cell | |
.append('text') | |
.attr('clip-path', d => 'url(#clip-' + d.data.id + ')') | |
.append('tspan') | |
.at({ | |
x: 8, | |
y: 8, | |
dy: '.8em', | |
class: 'playerNames', | |
}) | |
.text(function(d) { | |
// Only display text if sibling <rect> element is wide enough | |
const parentRect = this.parentNode.previousElementSibling; | |
if (d3.select(parentRect).classed('wide')) { | |
return d.data.name; | |
} | |
}); | |
cell | |
.append('text') | |
.attr('clip-path', d => 'url(#clip-' + d.data.id + ')') | |
.append('tspan') | |
.at({ | |
x: 8, | |
y: 20, | |
dy: '1.2em', | |
class: 'playerNamesFee', | |
}) | |
.text(function(d) { | |
// Only display text if sibling <rect> element is wide enough | |
const parentRect = this.parentNode.parentNode.firstElementChild; | |
if (d3.select(parentRect).classed('wide')) { | |
return '£' + d.data.fee.split('.')[0] + 'm'; | |
} | |
}); | |
// 💩 Manual labelling | |
const key = [ | |
{ name: 'Europe', color: '#254251' }, | |
{ name: 'Britain', color: '#E0AB26' }, | |
{ name: 'Africa', color: '#3292A6' }, | |
{ name: 'S. America', color: '#F37F2F' }, | |
]; | |
let legendConfig = { | |
x: width + 10, | |
y: 10, | |
height: 20, | |
}; | |
// Create a legend element | |
const legend = container | |
.append('g') | |
.at({ class: 'legendContainer' }) | |
.selectAll('g') | |
.data(key) | |
.enter() | |
.append('g') | |
.at({ class: 'legend' }) | |
.attr('transform', (d, i) => { | |
const leftmargin = 0; | |
const topmargin = 0; | |
const x = leftmargin + legendConfig.x; | |
const y = i * legendConfig.height + legendConfig.y + topmargin; | |
return 'translate(' + x + ',' + y + ')'; | |
}); | |
const legendTitle = container | |
.append('g') | |
.at({ class: 'legendTitle' }) | |
.attr('transform', (d, i) => { | |
const height = 20; | |
const x = legendConfig.x; | |
const y = i * height + legendConfig.y; | |
return 'translate(' + x + ',' + y + ')'; | |
}); | |
legendTitle.append('text').at({ x: 0, y: 20 }).text('Key'); | |
legend | |
.append('rect') | |
.at({ | |
width: 10, | |
height: 10, | |
}) | |
.translate([0, 30]) | |
.st({ | |
fill: d => d.color, | |
stroke: d => d.color, | |
}); | |
legend | |
.append('text') | |
.at({ | |
x: 20, | |
y: 40, | |
}) | |
.st({ color: '#666', fill: '#666' }) | |
.text(d => d.name); | |
const linewidth = config.width < 400 ? width - 20 : 100; | |
const lineheight = config.width < 400 ? 90 : 110; | |
legendTitle.append('line').at({ | |
x1: 0, | |
x2: linewidth, | |
y1: 0, | |
y2: 0, | |
strokeWidth: 2, | |
stroke: '#ddd', | |
}); | |
legendTitle.append('line').at({ | |
x1: 0, | |
x2: linewidth, | |
y1: lineheight, | |
y2: lineheight, | |
strokeWidth: 2, | |
stroke: '#ddd', | |
}); | |
// Create a legend element | |
const titleheight = height; | |
const titlemargin = 30; | |
const playerInfoContainer = svg | |
.append('g') | |
.attr('class', 'playerInfoContainer') | |
.selectAll('g') | |
.data(key) | |
.enter() | |
.append('g') | |
.attr('class', 'playerInfo') | |
.translate((d, i) => { | |
return [legendConfig.x, i * titleheight + 10]; | |
}); | |
const playerInfoTitle = svg | |
.append('g') | |
.attr('class', 'playerInfoTitle') | |
.translate([legendConfig.x, titleheight * 0.3 + titlemargin]); | |
playerInfoTitle.append('text').attr('x', 0).attr('y', 0); | |
const playerInfo = svg | |
.append('g') | |
.attr('class', 'playerInfo') | |
.translate([legendConfig.x, titleheight * 0.3 + titlemargin + 30]); | |
playerInfo | |
.append('text') | |
.at({ | |
class: 'playerInfo', | |
x: 0, | |
y: 10, | |
}) | |
.tspans(() => d3.wordwrap('Tap an area for more information', 20)); | |
// Appends player info on click on a rect | |
const appendPlayerInfo = (obj, data) => { | |
playerInfoTitle.html(''); | |
playerInfo.html(''); | |
playerInfo.append('text').at({ x: 0, y: 0, class: 'playerInfo' }); | |
playerInfoTitle | |
.append('text') | |
.at({ y: 0 }) | |
.text('£' + data.data.fee.split('.')[0] + 'm'); | |
playerInfo | |
.append('text') | |
.at({ y: -25 }) | |
.tspans(() => { | |
const { name, fromto } = data.data; | |
return d3.wordwrap(name + ' ' + fromto, 15); | |
}) | |
.attr('dy', (d, i) => i + 15); | |
}; | |
}); | |
const sumBySize = d => d.fee; | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment