Contact me at twitter @jalapic
Last active
September 9, 2015 02:16
-
-
Save jalapic/01b8ae143f94d65541d6 to your computer and use it in GitHub Desktop.
Soccer shotplot
This file contains 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://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<title>Shotplot</title> | |
<style> | |
h1{ | |
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
font-size: 26px; | |
font-style: bold; | |
} | |
h2{ | |
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
font-size: 22px; | |
font-style: bold; | |
} | |
p{ | |
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
font-size: 16px; | |
} | |
body { | |
background-color: #ccc; | |
} | |
svg { | |
background-color: white; | |
} | |
.d3-tip { | |
line-height: 1; | |
font-weight: bold; | |
font-size: 14px; | |
padding: 12px; | |
background: rgba(0, 0, 0, 0.8); | |
color: #fff; | |
border-radius: 2px; | |
} | |
/* Creates a small triangle extender for the tooltip */ | |
.d3-tip:after { | |
box-sizing: border-box; | |
display: inline; | |
font-size: 10px; | |
width: 100%; | |
line-height: 1; | |
color: rgba(0, 0, 0, 0.8); | |
content: "\25BC"; | |
position: absolute; | |
text-align: center; | |
} | |
/* Style northward tooltips differently */ | |
.d3-tip.n:after { | |
margin: -1px 0 0 0; | |
top: 100%; | |
left: 0; | |
} | |
</style> | |
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script> | |
</head> | |
<body> | |
<h1>Shotplots</h1> | |
<div id="shotplotContainerWrapper"> | |
<div id="shotplotContainer"> | |
</div> | |
</div> | |
<script> | |
// The basic image. | |
var holder = d3.select("div") | |
.append("svg") // append an SVG element to the div | |
.attr("width", 750) | |
.attr("height", 467); | |
// draw a rectangle - pitch | |
holder.append("rect") // attach a rectangle | |
.attr("x", 0) // position the left of the rectangle | |
.attr("y", 0) // position the top of the rectangle | |
.attr("height", 467) // set the height | |
.attr("width", 750) // set the width | |
.style("stroke-width", 0) // set the stroke width | |
.style("fill", "#80B280"); // set the fill colour | |
// draw penalty area | |
holder.append("rect") // attach a rectangle | |
.attr("x", 135) // position the left of the rectangle | |
.attr("y", 90) // position the top of the rectangle | |
.attr("height", 125) // set the height | |
.attr("width", 480) // set the width | |
.style("stroke-width", 4) // set the stroke width | |
.style("stroke", "white") // set the line colour | |
.style("fill", "#80B280"); // set the fill colour | |
// // draw a six yard box | |
holder.append("rect") // attach a rectangle | |
.attr("x", 252) // position the left of the rectangle | |
.attr("y", 90) // position the top of the rectangle | |
.attr("height", 47) // set the height | |
.attr("width", 246) // set the width | |
.style("stroke-width", 4) // set the stroke width | |
.style("stroke", "white") // set the line colour | |
.style("fill", "#80B280"); // set the fill colour | |
// // draw a circle - penalty spot 1 | |
holder.append("circle") // attach a circle | |
.attr("cx", 375) // position the x-centre | |
.attr("cy", 171) // position the y-centre | |
.attr("r", 3) // set the radius | |
.style("fill", "white"); // set the fill colour | |
// // penalty box semi-circle 1 | |
var arc = d3.svg.arc() | |
.innerRadius(68) | |
.outerRadius(72) | |
.startAngle(2.25) //radians | |
.endAngle(4.05) //just radians | |
holder.append("path") | |
.attr("d", arc) | |
.attr("fill", "white") | |
.attr("transform", "translate(375,171)"); | |
// center circle | |
var arc1 = d3.svg.arc() | |
.innerRadius(70) | |
.outerRadius(74) | |
.startAngle(6) //radians | |
.endAngle(-1) //just radians | |
holder.append("path") | |
.attr("d", arc1) | |
.attr("fill", "white") | |
.attr("transform", "translate(375,465)"); | |
// // add goal | |
holder.append("rect") // attach a rectangle | |
.attr("x", 333) // position the left of the rectangle | |
.attr("y", 57) // position the top of the rectangle | |
.attr("height", 33) // set the height | |
.attr("width", 84) // set the width | |
.style("stroke-width", 4) // set the stroke width | |
.style("stroke", "#EEEEEE") // set the line colour | |
.style("fill", "#80B280"); // set the fill colour | |
// draw goal line | |
holder.append("line") | |
.attr("x1", 0) | |
.attr("y1", 90) | |
.attr("x2", 750) | |
.attr("y2", 90) | |
.style("stroke-width", 4) // set the stroke width | |
.style("stroke", "white") // set the line colour | |
// draw half way line | |
holder.append("line") | |
.attr("x1", 0) | |
.attr("y1", 465) | |
.attr("x2", 750) | |
.attr("y2", 465) | |
.style("stroke-width", 4) // set the stroke width | |
.style("stroke", "white") // set the line colour | |
// TOOLTIP SETUP | |
var tip = d3.tip() | |
.attr('class', 'd3-tip') | |
.offset([-10, 0]) | |
.html(function(d) { | |
return "<strong>Opponent:</strong> <span style='color:red'>" + d.opp + "</span>" + "<br>" + | |
"<strong>Venue:</strong> <span style='color:lime'>" + d.ha + "</span>" + "<br>" + | |
"<strong>Time:</strong> <span style='color:orange'>" + d.time + "</span>" | |
; | |
}) | |
holder.call(tip); | |
// colors of dots | |
var color = d3.scale.ordinal() | |
.range(["blue", "red", "yellow", "lightgray", "peru"]); | |
// add points | |
d3.csv("playershots_remy1314.txt", function(error, dots) { | |
holder.append("g") | |
.selectAll("circle") | |
.data(dots) | |
.enter() | |
.append("circle") | |
.attr("r", 5) | |
.attr("cx", function(d) { return +d.d3x; }) | |
.attr("cy", function(d) { return +d.d3y; }) | |
.style("stroke-width", 2) // set the stroke width | |
.style("stroke", "black") // set the line colour | |
.style("fill", function(d) { return color(d.event)}) | |
.style("opacity", 0) // set the initialopacity | |
.on('mouseover', tip.show) | |
.on('mouseout', tip.hide) | |
.transition() | |
.delay(function(d,i){return i * 200}) | |
.duration(200) | |
.style("opacity", 1) // set the opacity | |
; | |
}); | |
// Legend 1 | |
var dataset = [ | |
{x: 600, y: 15, r: 4, color: "yellow", txt: "Goal"}, | |
{x: 600, y: 30, r: 4, color: "red", txt: "On-Target"}, | |
{x: 600, y: 45, r: 4, color: "blue", txt: "Off-Target"}, | |
{x: 600, y: 60, r: 4, color: "peru", txt: "Hit Woodwork"}, | |
{x: 600, y: 75, r: 4, color: "lightgray", txt: "Blocked"} | |
]; | |
holder.append("g") | |
.selectAll("circle") | |
.data(dataset) | |
.enter() | |
.append("circle") | |
.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }) | |
.attr("r", function(d) { return d.r; }) | |
.style("stroke-width", 2) // set the stroke width | |
.style("stroke", "black") // set the line colour | |
.style("fill", function(d) {return d.color;}) | |
; | |
var text = holder.append("g").selectAll("text") | |
.data(dataset) | |
.enter() | |
.append("text"); | |
var textLabels = text | |
.attr("x", function(d) { return d.x +7; }) | |
.attr("y", function(d) { return d.y +3; }) | |
.text( function (d) { return d.txt; }) | |
.attr("font-family", 'Arial') | |
.attr("font-size", "10px") | |
.attr("fill", "black"); | |
// Plot Title | |
var dataset1 = [ | |
{x: 25, y: 25, fs: "18px", txt: "Loïc Rémy"}, | |
{x: 25, y: 45, fs: "14px", txt: "Newcastle United"}, | |
{x: 25, y: 60, fs: "12px", txt: "2013/14"} | |
]; | |
var text1 = holder.append("g").selectAll("text") | |
.data(dataset1) | |
.enter() | |
.append("text"); | |
var textLabels1 = text1 | |
.attr("x", function(d) { return d.x; }) | |
.attr("y", function(d) { return d.y; }) | |
.text( function (d) { return d.txt; }) | |
.attr("font-family", 'Arial') | |
.attr("font-size", function (d) { return d.fs; }) | |
.attr("fill", "black"); | |
</script> | |
<p>A <a href="http://d3js.org/">d3.js</a> visualization of Loïc Rémy's shots throughout the 2013-14 season. The animation is a bit gratuitous but fun. <br> The dots appear in chronological order. Hover over a datapoint to find more information about each shot.</p> | |
<p> Code are <a href="https://github.com/jalapic/jalapic.github.io">available here </a>. Please get in touch via twitter if you have any feedback - <a href="https://twitter.com/jalapic">@jalapic</a> </p> | |
</body> | |
</html> |
This file contains 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
"event","d3x","d3y","opp","time","ha" | |
"Off",493,245,"Aston Villa","38","away" | |
"On",488,278,"Aston Villa","53","away" | |
"Off",180,238,"Hull City","3","home" | |
"Goal",385,125,"Hull City","10","home" | |
"Blocked",328,248,"Hull City","16","home" | |
"Off",362,182,"Hull City","41","home" | |
"Goal",254,170,"Hull City","44","home" | |
"Off",331,123,"Hull City","90+1'","home" | |
"Off",452,148,"Everton","68","away" | |
"Goal",367,124,"Everton","89","away" | |
"Off",436,236,"Everton","90+3'","away" | |
"On",403,151,"Cardiff City","18","away" | |
"On",333,184,"Cardiff City","27","away" | |
"Goal",460,261,"Cardiff City","30","away" | |
"Goal",498,221,"Cardiff City","38","away" | |
"Blocked",325,199,"Cardiff City","50","away" | |
"Off",440,333,"Liverpool","18","home" | |
"Blocked",302,277,"Liverpool","70","home" | |
"On",418,152,"Chelsea","61","home" | |
"Off",433,280,"Chelsea","61","home" | |
"Blocked",275,160,"Chelsea","72","home" | |
"On",250,257,"Chelsea","79","home" | |
"Blocked",400,148,"Chelsea","85","home" | |
"Blocked",292,236,"Chelsea","88","home" | |
"Goal",358,172,"Chelsea","89","home" | |
"Blocked",490,167,"Tottenham Hotspur","11","away" | |
"Goal",430,169,"Tottenham Hotspur","13","away" | |
"Goal",416,94,"Norwich City","2","home" | |
"Off",299,187,"Norwich City","26","home" | |
"Blocked",309,173,"Norwich City","33","home" | |
"Off",281,325,"Norwich City","44","home" | |
"Blocked",302,209,"Norwich City","90+2'","home" | |
"Blocked",363,148,"West Bromwich Albion","19","home" | |
"Off",555,276,"West Bromwich Albion","44","home" | |
"Off",380,160,"West Bromwich Albion","45+3'","home" | |
"On",450,229,"West Bromwich Albion","51","home" | |
"Off",286,246,"West Bromwich Albion","63","home" | |
"On",386,155,"Swansea City","12","away" | |
"Off",406,270,"Swansea City","30","away" | |
"Blocked",536,197,"Swansea City","41","away" | |
"Off",357,162,"Swansea City","56","away" | |
"Blocked",360,165,"Swansea City","58","away" | |
"Off",286,172,"Manchester United","18","away" | |
"Blocked",374,144,"Southampton","12","home" | |
"On",419,268,"Southampton","55","home" | |
"Blocked",339,222,"Southampton","57","home" | |
"Off",427,175,"Southampton","72","home" | |
"Goal",262,166,"Stoke City","44","home" | |
"Goal",404,101,"Stoke City","56","home" | |
"Blocked",230,190,"Stoke City","58","home" | |
"Blocked",296,252,"Stoke City","88","home" | |
"Off",240,208,"West Bromwich Albion","21","away" | |
"Off",240,208,"West Bromwich Albion","21","away" | |
"Blocked",353,274,"West Bromwich Albion","22","away" | |
"Off",540,200,"West Bromwich Albion","58","away" | |
"On",286,167,"Manchester City","69","home" | |
"Off",424,156,"Manchester City","90","home" | |
"Off",458,189,"West Ham United","8","away" | |
"On",448,292,"West Ham United","13","away" | |
"Goal",405,130,"West Ham United","33","away" | |
"On",218,175,"West Ham United","48","away" | |
"Off",394,232,"West Ham United","63","away" | |
"Off",464,247,"Norwich City","3","away" | |
"On",344,190,"Norwich City","18","away" | |
"Woodwork",528,212,"Norwich City","24","away" | |
"Blocked",542,200,"Norwich City","38","away" | |
"Blocked",446,250,"Norwich City","52","away" | |
"Woodwork",750,90,"Norwich City","60","away" | |
"On",377,185,"Norwich City","67","away" | |
"Blocked",396,232,"Norwich City","78","away" | |
"On",530,248,"Aston Villa","2","home" | |
"Off",340,241,"Aston Villa","15","home" | |
"Off",259,233,"Aston Villa","43","home" | |
"On",209,240,"Aston Villa","79","home" | |
"Woodwork",339,178,"Aston Villa","88","home" | |
"Goal",352,175,"Aston Villa","90+2'","home" | |
"Blocked",553,302,"Hull City","30","away" | |
"Goal",358,200,"Hull City","42","away" | |
"Off",286,277,"Hull City","63","away" | |
"On",396,231,"Hull City","70","away" | |
"Blocked",521,161,"Hull City","90","away" | |
"Off",380,155,"Swansea City","56","home" | |
"Off",183,278,"Swansea City","62","home" | |
"Blocked",356,172,"Cardiff City","1","home" | |
"Off",212,194,"Cardiff City","12","home" | |
"Blocked",235,149,"Cardiff City","78","home" | |
"Goal",346,140,"Cardiff City","87","home" | |
"Blocked",371,147,"Cardiff City","90+1'","home" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment