Skip to content

Instantly share code, notes, and snippets.

@pbeshai
Last active October 25, 2019 16:40
  • Star 5 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save pbeshai/ffd0f9d84b4e8df27db2 to your computer and use it in GitHub Desktop.
Shooting Signatures

Shooting Signatures were designed to give an at-a-glance view of an NBA player's shooting performance. They were developed by Peter Beshai (@pbesh) as part of Buckets, an interactive NBA shot visualization tool.

Shooting Signature Explanation

Sample Shooting Signature usage

Shooting Signatures of NBA Players from the 2013-14 Season
<!DOCTYPE html>
<html>
<head>
<title>Shooting Signature Example</title>
</head>
<body>
<div id="signature"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script>
// get your data
var data = [{"x":0,"y":0.7984084880636605,"widthValue":158,"colorValue":0.1629224750896936},{"x":1,"y":0.765993265993266,"widthValue":145,"colorValue":0.16595424641159695},{"x":2,"y":0.7241379310344827,"widthValue":74,"colorValue":0.14788844079931118},{"x":3,"y":0.6082191780821917,"widthValue":36,"colorValue":0.09604064011114222},{"x":4,"y":0.45348837209302323,"widthValue":35,"colorValue":0.0205093443427537},{"x":5,"y":0.37333333333333335,"widthValue":39,"colorValue":-0.015647382920110142},{"x":6,"y":0.3744075829383886,"widthValue":39,"colorValue":-0.012850902701298128},{"x":7,"y":0.3926940639269407,"widthValue":37,"colorValue":0.0016039868744902042},{"x":8,"y":0.4619047619047619,"widthValue":22,"colorValue":0.07160931036145685},{"x":9,"y":0.4788135593220339,"widthValue":45,"colorValue":0.0874320387619314},{"x":10,"y":0.4979423868312757,"widthValue":45,"colorValue":0.10551911317398666},{"x":11,"y":0.4542253521126761,"widthValue":42,"colorValue":0.06070052516549129},{"x":12,"y":0.4503311258278146,"widthValue":44,"colorValue":0.05378653654579513},{"x":13,"y":0.4110787172011662,"widthValue":66,"colorValue":0.009121101429431566},{"x":14,"y":0.4196185286103542,"widthValue":61,"colorValue":0.01780232246319491},{"x":15,"y":0.42819148936170215,"widthValue":64,"colorValue":0.02163884827966278},{"x":16,"y":0.41988950276243087,"widthValue":71,"colorValue":0.016918338868285643},{"x":17,"y":0.45425867507886436,"widthValue":50,"colorValue":0.04975728463782164},{"x":18,"y":0.4141791044776119,"widthValue":45,"colorValue":0.015415540872963762},{"x":19,"y":0.4752475247524752,"widthValue":37,"colorValue":0.07919199741203164},{"x":20,"y":0.41830065359477125,"widthValue":20,"colorValue":0.030376008752275918},{"x":21,"y":0.4928571428571429,"widthValue":13,"colorValue":0.10821499731174294},{"x":22,"y":0.45592705167173253,"widthValue":18,"colorValue":0.07432771905345104},{"x":23,"y":0.43107221006564544,"widthValue":39,"colorValue":0.05587993676927633},{"x":24,"y":0.4125364431486881,"widthValue":221,"colorValue":0.041212020305431696},{"x":25,"y":0.3859060402684564,"widthValue":127,"colorValue":0.023158322960854794},{"x":26,"y":0.36511156186612576,"widthValue":60,"colorValue":0.00876771640727797},{"x":27,"y":0.32627118644067793,"widthValue":22,"colorValue":-0.017285514590249906},{"x":28,"y":0.25555555555555554,"widthValue":3,"colorValue":-0.07036667774454414},{"x":29,"y":0.34375,"widthValue":2,"colorValue":0.043655332912590716},{"x":30,"y":0.2,"widthValue":0,"colorValue":-0.06235399820305482}];
// initialize SVG
var width = 600, height = 200;
var svg = d3.select("#signature").append("svg")
.attr("width", width)
.attr("height", height);
// x = distance in shooting signatures
var x = d3.scale.linear()
.domain([0, 30])
.range([0, width]);
// for gradient offset (needs % - so map x domain to 0-100%)
var offset = d3.scale.linear()
.domain(x.domain())
.range([0, 100]);
// y = Field Goal % in shooting signatures
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
// scale for the width of the signature
var minWidth = 1;
var maxWidth = height / 4;
var w = d3.scale.linear()
.domain([0, 250])
.range([minWidth, maxWidth]);
// NOTE: if you want maxWidth to truly be the maximum width of the signature,
// you'll need to add .clamp(true) to w.
// need two area plots to make the signature extend in width in both directions around the line
var areaAbove = d3.svg.area()
.x(function(d) { return x(d.x); })
.y0(function (d) { return y(d.y) - w(d.widthValue); })
.y1(function(d) { return Math.ceil(y(d.y)); }) // ceil and floor prevent line between areas
.interpolate("basis");
var areaBelow = d3.svg.area()
.x(function(d) { return x(d.x); })
.y0(function (d) { return y(d.y) + w(d.widthValue); })
.y1(function(d) { return Math.floor(y(d.y)); }) // ceil and floor prevent line between areas
.interpolate("basis");
// add the areas to the svg
var gArea = svg.append("g").attr("class", "area-group");
gArea.append("path")
.datum(data)
.attr("class", "area area-above")
.attr("d", areaAbove)
.style("fill", "url(#area-gradient)"); // specify the linear gradient #area-gradient as the colouring
// NOTE: the colouring won't work if you have multiple signatures on the same page.
// In this case, you'll need to generate unique IDs for each gradient.
gArea.append("path")
.datum(data)
.attr("class", "area area-below")
.attr("d", areaBelow)
.style("fill", "url(#area-gradient)");
/*
// you can draw the line the signature is based around using the following code:
var line = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); })
.interpolate("basis");
gArea.append("path")
.datum(data)
.attr("d", line)
.style("stroke", "#fff")
.style("fill", "none")
*/
// set-up colours
var colorSchemes = {
buckets: {
domain: [-0.15, 0.15],
range: ["#405A7C", "#7092C0", "#BDD9FF", "#FFA39E", "#F02C21", "#B80E05"]
},
goldsberry: {
domain: [-0.15, 0.15],
range: ["#5357A1", "#6389BA", "#F9DC96", "#F0825F", "#AE2A47"]
}
};
var activeColorScheme = colorSchemes.goldsberry;
// Note that the quantize scale does not interpolate between colours
var colorScale = d3.scale.quantize()
.domain(activeColorScheme.domain)
.range(activeColorScheme.range);
// generate colour data
var colorData = [];
var stripe = false; // set stripe to true to prevent linear gradient fading
for (var i = 0; i < data.length; i++) {
var prevData = data[i - 1];
var currData = data[i];
if (stripe && prevData) {
colorData.push({
offset: offset(currData.x) + "%",
stopColor: colorScale(prevData.colorValue)
});
}
colorData.push({
offset: offset(currData.x) + "%",
stopColor: colorScale(currData.colorValue)
});
}
// generate the linear gradient used by the signature
gArea.append("linearGradient")
.attr("id", "area-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("y1", 0)
.attr("y2", 0)
.selectAll("stop")
.data(colorData)
.enter().append("stop")
.attr("offset", function(d) { return d.offset })
.attr("stop-color", function (d) { return d.stopColor; });
</script>
</body>
</html>
@timelyportfolio
Copy link

thanks so much for sharing the code behind these beautiful and magical creations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment