Skip to content

Instantly share code, notes, and snippets.

@jalapic
Last active September 9, 2015 02:16
Show Gist options
  • Save jalapic/01b8ae143f94d65541d6 to your computer and use it in GitHub Desktop.
Save jalapic/01b8ae143f94d65541d6 to your computer and use it in GitHub Desktop.
Soccer shotplot

Soccer shotplots with d3.js

Contact me at twitter @jalapic

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