Skip to content

Instantly share code, notes, and snippets.

@CafeConVega
Created February 7, 2016 18:34
Show Gist options
  • Save CafeConVega/bc09d99262b2683d0e0f to your computer and use it in GitHub Desktop.
Save CafeConVega/bc09d99262b2683d0e0f to your computer and use it in GitHub Desktop.
Week4: Sortable Heatmap Table
h1,
h2 {
font-family: 'Graduate', cursive;
text-align: center;
text-transform: uppercase;
}
p {
font-family: 'Roboto', sans-serif;
text-align: center;
}
table {
font-family: 'Roboto', sans-serif;
font-size: .9em;
width: 80vw;
margin-left: auto;
margin-right: auto;
text-align: center;
table-layout: fixed;
border-collapse: collapse;
border-spacing: .5em;
}
td {
padding: .5em;
border-right: 1px dotted #E6E6E6;
}
td:last-of-type {
border-right: 0px;
}
thead tr {
border-bottom: 1px solid black;
cursor: pointer;
}
thead tr:hover {
cursor: grab;
}
tbody tr:hover {
color: white;
background-color: darkgray;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>MIA vs. HOU Game Stats</title>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.0.min.js"></script>
<script type="text/javascript" src="stupidtable.js"></script>
<link href="custom.css" rel="stylesheet" type="text/css" />
<link href='https://fonts.googleapis.com/css?family=Graduate|Roboto' rel='stylesheet' type='text/css'>
</head>
<body>
<h1>Houston Texans at Miami Dolphins</h1>
<h2>October 26, 2015 1:00 PM - Sun Life Stadium - Miami, FL</h2>
<p>Source: <a href="http://www.pro-football-reference.com/" target="_blank">Pro-Football-Reference</a></p>
<h2>Individual Game Statistics - Receptions</h2>
<div id="table_rec" class="table"></div>
<script>
//Load in contents of CSV file
d3.csv("mia-hou_off_individual_stats.csv", function (error, data) {
if (error) {
console.log(error); //Log the error.
} else {
console.log(data); //Log the data.
}
//Vars for subsets of the raw data for different tables
var data_pass = [];
var data_rush = [];
var data_rec = [];
var rec_data = [];
var rec_yards_data = [];
var rec_avg_data = [];
// Format number fields
data.forEach(function (d) {
d.pass_comp = +d.pass_comp;
d.pass_att = +d.pass_att;
d.pass_comp_rate = Math.round(d.pass_comp/d.pass_att*100); //Calculated field
d.pass_yards = +d.pass_yards;
d.pass_avg_comp = Math.round(d.pass_yards/d.pass_comp); //Calculated field
d.pass_td = +d.pass_td;
d.pass_int = +d.pass_int;
d.pass_sack = +d.pass_sack;
d.pass_sack_yards = +d.pass_sack_yards;
d.pass_long = +d.pass_long;
d.pass_qbr = +d.pass_qbr;
d.rush_att = +d.rush_att;
d.rush_yards = +d.rush_yards;
d.rush_avg = Math.round(d.rush_yards/d.rush_att); //Calculated field
d.rush_td = +d.rush_td;
d.rush_long = +d.rush_long;
d.rec_target = +d.rec_target;
d.rec_rec = +d.rec_rec;
d.rev_catch_rate = Math.round(d.rec_rec/d.rec_target*100); //Calculated field
d.rec_yards = +d.rec_yards;
d.rec_avg = Math.round(d.rec_yards/d.rec_rec); //Calculated field
d.rec_td = +d.rec_td;
d.rec_long = +d.rec_long;
d.fmb = +d.fmb;
d.fmb_lost = +d.fmb_lost;
//Fix averages
if(d.rec_rec === 0) {
d.rec_avg = 0;
}
});
//Sort data by reception yards
data.sort(function (a, b) {
return b.rec_yards - a.rec_yards;
});
//Add data to reception arrays
data.forEach(function (d) {
if(d.rec_target > 0) {
data_rec.push([d.team, d.player, d.position, d.rec_target, d.rec_rec, d.rec_yards, d.rec_avg, d.rec_long, d.rec_td, d.fmb, d.fmb_lost]);
rec_data.push([d.rec_rec]);
rec_yards_data.push([d.rec_yards]);
rec_avg_data.push([d.rec_avg]);
}
});
console.log(data_rec);
//Pass Table
var table = d3.select("#table_rec").append("table");
var header = table.append("thead").append("tr");
var headerObjs = [
{ label: "TEAM", sort_type: "string" },
{ label: "PLAYER", sort_type: "string" },
{ label: "POSITION", sort_type: "string" },
{ label: "TARGETS", sort_type: "float" },
{ label: "RECEPTIONS", sort_type: "float" },
{ label: "YARDS", sort_type: "float" },
{ label: "AVG YARDS", sort_type: "float" },
{ label: "LONG RECEPTION", sort_type: "float" },
{ label: "TD", sort_type: "float" },
{ label: "FUMBLES", sort_type: "float" },
{ label: "LOST FUMBLES", sort_type: "float" },
];
header
.selectAll("th")
.data(headerObjs)
.enter()
.append("th")
.attr("data-sort", function (d) { return d.sort_type; })
.text(function(d) { return d.label; });
var tablebody = table.append("tbody");
rows = tablebody
.selectAll("tr")
.data(data_rec)
.enter()
.append("tr");
var white = "#FFFFFF";
var red = "#980000";
var green = "#7DC15A";
//Colorscale receptions
var colorScale_rec = d3.scale.linear()
.domain(d3.extent(rec_data))
.range([white, green]);
//Colorscale yards
var colorScale_yards = d3.scale.linear()
.domain(d3.extent(rec_yards_data))
.range([white, green]);
//Colorscale yards avg
var colorScale_rec_avg = d3.scale.linear()
.domain(d3.extent(rec_avg_data))
.range([white, green]);
cells = rows.selectAll("td")
.data(function(d) {
return d;
})
.enter()
.append("td")
.style("background-color", function(d,i) {
// Color receptions background
if (i === 4) {
return colorScale_rec(d);
}
// Color yards background
if (i === 5) {
return colorScale_yards(d);
}
// Color avg background
if (i === 6) {
return colorScale_rec_avg(d);
}
})
.text(function(d) {
return d;
});
$("#table_rec table").stupidtable();
});
</script>
</body>
</html>
player team position pass_comp pass_att pass_yards pass_td pass_int pass_sack pass_sack_yards pass_long pass_qbr rush_att rush_yards rush_td rush_long rec_target rec_rec rec_yards rec_td rec_long fmb fmb_lost
Brian Hoyer HOU QB 23 49 273 3 1 4 22 27 76.3 0 0 0 2 0
Arian Foster HOU RB 18 59 1 11 7 5 66 1 26
Nate Washington HOU WR 16 9 127 2 27
DeAndre Hopkins HOU WR 12 6 50 0 11 1 0
Chris Polk HOU RB 4 4 0 5 3 2 14 0 8
Keith Mumphery HOU WR 3 1 16 0 16
Alfred Blue HOU RB 3 8 0 3 0 0 0
Jaelen Strong HOU WR 1 0 0 0 0
Garrett Graham HOU TE 4 0 0 0 0
Jonathan Grimes HOU RB 1 0 0 0 0
C.J. Fiedorowicz HOU TE 2 0 0 0 0
Lamar Miller MIA RB 14 175 1 85 3 3 61 1 54
Ryan Tannehill MIA QB 18 19 282 4 0 4 41 54 158.3 1 3 0 3 0 0 0
Jarvis Landry MIA WR 1 5 0 5 5 5 83 2 50
Rishard Matthews MIA WR 3 3 75 1 53
Jonas Gray MIA RB 12 48 0 16 1 1 10 0 10
Greg Jennings MIA WR 2 2 37 0 23
Damien Williams MIA RB 4 19 0 19 2 2 10 0 8 1 1
Jordan Cameron MIA TE 2 2 23 0 12
Matt Moore MIA QB 1 1 14 0 0 0 0 14 118.7 3 -2 0 0 0 0 0
Dion Sims MIA TE 2 1 -3 0 -3
// Stupid jQuery table plugin.
(function($) {
$.fn.stupidtable = function(sortFns) {
return this.each(function() {
var $table = $(this);
sortFns = sortFns || {};
sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
$table.data('sortFns', sortFns);
$table.on("click.stupidtable", "thead th", function() {
$(this).stupidsort();
});
});
};
// Expects $("#mytable").stupidtable() to have already been called.
// Call on a table header.
$.fn.stupidsort = function(force_direction){
var $this_th = $(this);
var th_index = 0; // we'll increment this soon
var dir = $.fn.stupidtable.dir;
var $table = $this_th.closest("table");
var datatype = $this_th.data("sort") || null;
// No datatype? Nothing to do.
if (datatype === null) {
return;
}
// Account for colspans
$this_th.parents("tr").find("th").slice(0, $(this).index()).each(function() {
var cols = $(this).attr("colspan") || 1;
th_index += parseInt(cols,10);
});
var sort_dir;
if(arguments.length == 1){
sort_dir = force_direction;
}
else{
sort_dir = force_direction || $this_th.data("sort-default") || dir.ASC;
if ($this_th.data("sort-dir"))
sort_dir = $this_th.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
}
$table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
// Run sorting asynchronously on a timout to force browser redraw after
// `beforetablesort` callback. Also avoids locking up the browser too much.
setTimeout(function() {
// Gather the elements for this column
var column = [];
var sortFns = $table.data('sortFns');
var sortMethod = sortFns[datatype];
var trs = $table.children("tbody").children("tr");
// Extract the data for the column that needs to be sorted and pair it up
// with the TR itself into a tuple. This way sorting the values will
// incidentally sort the trs.
trs.each(function(index,tr) {
var $e = $(tr).children().eq(th_index);
var sort_val = $e.data("sort-value");
// Store and read from the .data cache for display text only sorts
// instead of looking through the DOM every time
if(typeof(sort_val) === "undefined"){
var txt = $e.text();
$e.data('sort-value', txt);
sort_val = txt;
}
column.push([sort_val, tr]);
});
// Sort by the data-order-by value
column.sort(function(a, b) { return sortMethod(a[0], b[0]); });
if (sort_dir != dir.ASC)
column.reverse();
// Replace the content of tbody with the sorted rows. Strangely
// enough, .append accomplishes this for us.
trs = $.map(column, function(kv) { return kv[1]; });
$table.children("tbody").append(trs);
// Reset siblings
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
$this_th.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
$table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
$table.css("display");
}, 10);
return $this_th;
};
// Call on a sortable td to update its value in the sort. This should be the
// only mechanism used to update a cell's sort value. If your display value is
// different from your sort value, use jQuery's .text() or .html() to update
// the td contents, Assumes stupidtable has already been called for the table.
$.fn.updateSortVal = function(new_sort_val){
var $this_td = $(this);
if($this_td.is('[data-sort-value]')){
// For visual consistency with the .data cache
$this_td.attr('data-sort-value', new_sort_val);
}
$this_td.data("sort-value", new_sort_val);
return $this_td;
};
// ------------------------------------------------------------------
// Default settings
// ------------------------------------------------------------------
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
$.fn.stupidtable.default_sort_fns = {
"int": function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
},
"float": function(a, b) {
return parseFloat(a) - parseFloat(b);
},
"string": function(a, b) {
return a.localeCompare(b);
},
"string-ins": function(a, b) {
a = a.toLocaleLowerCase();
b = b.toLocaleLowerCase();
return a.localeCompare(b);
}
};
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment