Skip to content

Instantly share code, notes, and snippets.

@kirkhunter
Last active June 23, 2016 22:22
Show Gist options
  • Save kirkhunter/d6de36919bd54f52479d214050c71c16 to your computer and use it in GitHub Desktop.
Save kirkhunter/d6de36919bd54f52479d214050c71c16 to your computer and use it in GitHub Desktop.
Visualizing NCAA Finals Games
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<style>
body {
font-family: arial;
width: 960px;
}
h2.title {
color: black;
text-align: center;
font-family: arial;
}
.axis {
font-family: arial;
font-size: 0.8em;
}
.axis text {
fill: black;
stroke: none;
}
path {
fill: none;
stroke: black;
stroke-width: 2px;
}
circle {
opacity: .95;
stroke: none;
}
.tick {
fill: none;
stroke: black;
}
div.year_buttons {
text-align: center;
}
div.year_buttons div {
text-align: center;
background-color: #c83c8b /*#3c68c8; */
color: white;
padding: 6px;
margin: 5px;
font-size: 0.8em;
cursor: pointer;
display: inline-block;
}
</style>
<script type="text/javascript">
function draw(data) {
/*
D3.js setup code
*/
"use strict";
// set margins according to Mike Bostock's margin conventions
// http://bl.ocks.org/mbostock/3019563
var margin = {top: 25, right: 40, bottom: 25, left: 75};
// set height and width of chart
var width = 910 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom,
radius = 5;
// Append the title for the graph
d3.select("body")
.append("h2")
.text("NCAA March Madness Finals")
.attr('class', 'title');
// append the SVG tag with height and width to accommodate for margins
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append('g')
.attr('class','chart')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// object to keep track on animation state
var animation = {
"timeout": false,
"animating": false
};
// Create x-axis scale mapping dates -> pixels
var xScale = d3.scale.linear()
.range([0, width])
.domain([0, d3.max(data, function(d) {return d.Wscore} ) ]);
// Create y-axis scale mapping temperature -> pixels
var yScale = d3.scale.linear()
.range([height, 0])
.domain([0, d3.max(data, function(d) {return d.Wscore})]);
var lin_scale_x = d3.scale.linear().domain([0, 100]).range([0, width]);
var lin_scale_y = d3.scale.linear().domain([0, 100]).range([height, 0]);
// Create D3 axis object from time_scale for the x-axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
// Create D3 axis object from temp_scale for the y-axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
// Append SVG to page corresponding to the x-axis
svg.append('g')
.attr('class', 'x axis')
.attr('transform', "translate(0," + height + ")")
.call(xAxis);
// Append SVG to page corresponding to the y-axis
svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
// add label to y-axis
d3.select(".y.axis")
.append("text")
.text("Winning Team")
.attr("transform", "rotate(-90, -43, 0) translate(-260)")
.style('font-size', '1.4em')
.style('font-family', 'arial');
var line_dat = [
{x:0, y:0},
{x:50, y:50},
{x:90, y:90}
];
// define the values to map for x and y position of the line
var line = d3.svg.line()
.x(function(d) {return lin_scale_x(+d.x)})
.y(function(d) {return lin_scale_y(+d.y)});
// append a SVG path that corresponds to the line chart
var path = svg.append("path")
.datum(line_dat)
.attr("class", "line")
.style("stroke-dasharray", ("8, 7"))
.style("stroke", "#438CCA")
.attr("d", line);
var legend_dat = [
{type: "Offense", color: "#4b72cc"},
{type: "Defense", color: "black"},
{type: "Missed Shots/Turnovers", color: "#f57b56"}
];
var legend = svg.selectAll(".legend")
.data(legend_dat)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", 20)
.attr("y", 12)
.attr("width", 13)
.attr("height", 13)
.style("fill", function(d) {return d.color});
legend.append("text")
.attr("x", 44)
.attr("y", 19)
.attr("dy", ".39em")
.style("text-anchor", "begin")
.text(function(d) { return d.type; });
// extract the years we are visualizing
// (to be used as labels in buttons)
var years = data.map(function(d, i) {
return {
key: d.Season,
selected: false,
idx: i
};
});
// filter our data for the specified year in 'key'
var sub = data.filter(function(d) {
return d.Season === "2003";
})[0];
var scoring = "#4b72cc";
var miss = "#f57b56";
var year_data = [
{x: sub.Lscore, y: sub.Wscore, color: scoring, stat: "Final Score" },
{x: sub.Lfgm, y: sub.Wfgm, color: scoring, stat: "Field Goals Made" },
{x: sub.Lftm, y: sub.Wftm, color: scoring, stat: "Free Throws Made"},
{x: sub.Lfgm3, y: sub.Wfgm3, color: scoring, stat: "3-Pointers Made"},
{x: sub.Lblk, y: sub.Wblk, color: "black", stat: "Blocks"},
{x: sub.Last, y: sub.Wast, color: scoring, stat: "Assists"},
{x: sub.Lstl, y: sub.Wstl, color: "black", stat: "Steals"},
{x: sub.Lto, y: sub.Wto, color: miss, stat: "Turnovers"},
{x: sub.Lor, y: sub.Wor, color: "black", stat: "Off. Rebounds"},
{x: sub.Ldr, y: sub.Wdr, color: "black", stat: "Def. Rebounds"},
// {x: sub.Lfga, y: sub.Wfga, color: "", stat: "Field Goals Attempted"},
// {x: sub.Lfta, y: sub.Wfta, color:, stat: "Free Throws Attempted"},
// {x: sub.Lfga3, y: sub.Wfga3, color:, stat: "3-pointers Attempted"},
{x: sub.Lpf, y: sub.Wpf, color: "black", stat: "Personal Fouls"},
{x: sub.Lfga-sub.Lfgm, y: sub.Wfga-sub.Wfgm, color: miss, stat: "Field Goals Missed"},
{x: sub.Lfta-sub.Lftm, y: sub.Wfta-sub.Wftm, color: miss, stat: "Free Throws Missed"},
{x: sub.Lfga3-sub.Lfgm3, y: sub.Wfga3-sub.Wfgm3, color: miss, stat: "3-Pointers Missed"}
];
var circleAttrs = {
cx: function(d) {return xScale(d.x)},
cy: function(d) {return yScale(d.y)},
r: radius,
fill: function(d) {return d.color}
};
svg.selectAll("circle")
.data(year_data)
.enter()
.append("circle")
.attr(circleAttrs) // Get attributes from circleAttrs var
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);
svg.append("text")
.attr("id", "wteam-name")
.attr("x", width - 93)
.attr("y", height - 55)
.text("W: " + sub.Wteam_name);
svg.append("text")
.attr("id", "lteam_name")
.attr("x", width - 93)
.attr("y", height - 35)
.text("L: " + sub.Lteam_name);
// Find min/max of points column
var points_extent = d3.extent(data, function(d) {
return d['Wscore'];
});
// dynamically create buttons to toggle years
var buttons = d3.select("body")
.append("div")
.attr("class", "year_buttons")
.selectAll("div")
.data(years)
.enter()
.append("div")
.text(function(d) {
return d.key;
})
.attr('id', function(d) {
// convert mm/dd to mm_dd for more readable #id
return 'd' + d.key.split('/').join('_');
});
// toggle button of first year
d3.select('.year_buttons')
.select('div:first-child')
.transition()
.duration(250)
.style("background", "#f57b56")
.style('color', 'black');
// create a timeout interval to animate over the years
var interval_timeout = function(year_idx) {
// set timeout to run callback function every 2000 milliseconds
var year_interval = setInterval(function(){
// run the update function to update the bound data appropriately
update('' + year_idx);
// increment the year visualized by one
year_idx++;
if(year_idx >= (year_data.length + 2003)) {
// clear callback if we have reached the end of years
clearInterval(year_interval);
}
}, 2000);
// change animation state on the global object to true
animation.animating = true;
// return a function we can use to stop the animation
return function() {
clearInterval(year_interval);
};
};
// On mouseover, we want to display text and highlight circle
svg.on("mouseover", function() {
var coords = d3.mouse(this);
svg.selectAll("circle") // For new circle, go through the update process
.data(year_data) // <--! need to give this appropriate data ---------------------->
.enter()
.attr(circleAttrs) // Get attributes from circleAttrs var
.on("mouseover", handleMouseOver)
.on("mouseout", handleMouseOut);
});
// Create Event Handlers for mouse
function handleMouseOver(d, i) { // Add interactivity
// Use D3 to select element, change color and size
d3.select(this).attr({
fill: "orange",
r: radius * 2
});
// Specify where to put label of text
svg.append("text").attr({
id: "t" + d.x + "-" + d.y + "-" + i, // Create an id for text so we can select it later for removing on mouseout
x: function() { return xScale(d.x) - 30; },
y: function() { return yScale(d.y) - 15; }
})
.text(function() {
return d.stat; // Value of the text
});
};
function handleMouseOut(d, i) {
// Use D3 to select element, change color back to normal
d3.select(this).attr({
fill: d.color,
r: radius
});
// Select text by id and then remove
d3.select("#t" + d.x + "-" + d.y + "-" + i).remove(); // Remove text location
};
// define a function to unselect all the buttons
var button_unhighlight = function() {
d3.select(".year_buttons")
.selectAll("div")
.datum(function(d) {
// change the internal state of each button to unselected
d.selected = false;
return d;
})
.transition()
.duration(250)
.style("color", "white")
.style("background", '#4b72cc');
};
// highlight button with a specified id
var button_highlight = function(id) {
d3.select('#' + 'd' + id)
.datum(function(d) {
// change internal state to selected
d.selected = true;
return d;
})
.transition()
.duration(250)
.style("background", "#f3904d")
.style('color', 'black');
};
// add hover to buttons
buttons.on('mouseover', function(d) {
if (!d.selected) {
d3.select(this)
.transition()
.duration(250)
.style("background", "#f3904d")
.style('color', 'black');
}
});
// add hover to buttons
buttons.on('mouseout', function(d) {
if (!d.selected) {
d3.select(this)
.transition()
.duration(250)
.style("color", "white")
.style("background", '#4b72cc');
} else {
d3.select(this)
.transition()
.duration(250)
.style("background", "#f3904d")
.style('color', 'black');
}
});
// function to be run when on a clicked button
buttons.on("click", function(d) {
if (d.selected === true) {
// if button is selected an the animation is stopped
if (animation.animating === false) {
// change internal state of this button
d.selected = false;
button_unhighlight();
// start the animation on the next button
animation.timeout = interval_timeout(d.idx + 1);
} else {
// if the button is selected but animation
// is running, stop the animation
animation.timeout();
animation.animating = false;
}
} else {
// if button is not selected, selected it
d.selected = true;
// stop the animation
animation.animating = false;
animation.timeout();
};
// update the bound data for this button
d3.select(this).datum(d);
// run our update function for the line chart
update(d.key);
});
// function which updates the data bound to our chart
function update(key) {
// filter our data for the specified year in 'key'
console.log(key);
var subb = data.filter(function(d) {
return d.Season === key;
})[0];
console.log(subb);
var year_dat = [
{x: subb.Lscore, y: subb.Wscore, color: scoring, stat: "Final Score" },
{x: subb.Lfgm, y: subb.Wfgm, color: scoring, stat: "Field Goals Made" },
{x: subb.Lftm, y: subb.Wftm, color: scoring, stat: "Free Throws Made"},
{x: subb.Lfgm3, y: subb.Wfgm3, color: scoring, stat: "3-Pointers Made"},
{x: subb.Lblk, y: subb.Wblk, color: "black", stat: "Blocks"},
{x: subb.Last, y: subb.Wast, color: scoring, stat: "Assists"},
{x: subb.Lstl, y: subb.Wstl, color: "black", stat: "Steals"},
{x: subb.Lto, y: subb.Wto, color: miss, stat: "Turnovers"},
{x: subb.Lor, y: subb.Wor, color: "black", stat: "Off. Rebounds"},
{x: subb.Ldr, y: subb.Wdr, color: "black", stat: "Def. Rebounds"},
// {x: subb.Lfga, y: subb.Wfga, color: "", stat: "Field Goals Attempted"},
// {x: subb.Lfta, y: subb.Wfta, color:, stat: "Free Throws Attempted"},
// {x: subb.Lfga3, y: subb.Wfga3, color:, stat: "3-pointers Attempted"},
{x: subb.Lpf, y: subb.Wpf, color: "black", stat: "Personal Fouls"},
{x: subb.Lfga-subb.Lfgm, y: subb.Wfga-subb.Wfgm, color: miss, stat: "Field Goals Missed"},
{x: subb.Lfta-subb.Lftm, y: subb.Wfta-subb.Wftm, color: miss, stat: "Free Throws Missed"},
{x: subb.Lfga3-subb.Lfgm3, y: subb.Wfga3-subb.Wfgm3, color: miss, stat: "3-Pointers Missed"}
];
// highlight buttons in sync with animation
button_unhighlight();
button_highlight(key);
// Display team names in lower right corner of chart
svg.append("rect")
.attr("x", width - 90)
.attr("y", height - 100)
.attr("width", 125)
.attr("height", 75)
.style("fill", "white");
svg.append("text")
.attr("id", "wteam-name")
.attr("x", width - 93)
.attr("y", height - 55)
.text("W: " + subb.Wteam_name);
svg.append("text")
.attr("id", "lteam-name")
.attr("x", width - 93)
.attr("y", height - 35)
.text("L: " + subb.Lteam_name);
svg.selectAll("circle")
.data(year_dat)
// .enter()
// .append("circle")
.transition()
.duration(700)
.attr(circleAttrs); // Get attributes from circleAttrs var
// .on("mouseover", handleMouseOver)
// .on("mouseout", handleMouseOut);
// On mouseover, we want to display text and highlight circle
svg.on("mouseover", function() {
var coords = d3.mouse(this);
svg.selectAll("circle") // For new circle, go through the update process
.data(year_dat)
.enter()
.attr(circleAttrs); // Get attributes from circleAttrs var
// .on("mouseover", handleMouseOver)
// .on("mouseout", handleMouseOut);
});
};
// start initial animation for first year
animation.timeout = interval_timeout(2004);
};
</script>
</head>
<body>
<script type="text/javascript">
d3.tsv("https://raw.githubusercontent.com/KirkHunter/KirkHunter.github.io/master/march-madness-finals-2003_2015.csv", draw);
</script>
</body>
</html>
Kirk Hunter
MSAN 622 Data Visualization
21 April 2016
Project 2 Thesis Statement and Motivation
The NCAA college basketball tournament championship games over the last thirteen seasons
have been won and lost by rebounds and free throws.
If one were to predict the outcome of these championship games using only one metric, then
it would be a toss­up between number of defensive rebounds and number of free throws made.
In each case, the team that led in overall defensive rebounds or number of free throws made
won the championship nine out of thirteen times ­­ around seventy percent of the time.
And only once in the last thirteen seasons did the losing team end the game leading in both
defensive rebounds and free throws made, while in seven championship games the winning team
ended the game having led in both metrics.
The type of narrative I will choose to visualize the data is from the author’s perspective.
I believe my message is clear, and the point of the visualization should be to communicate
that message, as opposed to leaving it up to the reader to interpret what they will. The
importance of visualizing this dataset in particular is not as pressing as other topics,
but it is relevant to many nonetheless. Sports analytics is a growing field, and my most
optimistic outcome for this project is that I can allow for some insight into the patterns
that emerge in the largest national college basketball tournament in the United States.
Another important outcome that I would like to see realized is to generate interest into
the sport or interest for sports analytics at the college level.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment