Project Description: Visualizing the last thirteen NCAA March Madness championship games in d3.
the original dataset can be found here https://www.kaggle.com/c/march-machine-learning-mania-2016/data
Project Description: Visualizing the last thirteen NCAA March Madness championship games in d3.
the original dataset can be found here https://www.kaggle.com/c/march-machine-learning-mania-2016/data
<!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 tossup 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. |