Skip to content

Instantly share code, notes, and snippets.

@Lulkafe
Last active June 22, 2016 05:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lulkafe/54219f02b4e1624d664b0913550da143 to your computer and use it in GitHub Desktop.
Save Lulkafe/54219f02b4e1624d664b0913550da143 to your computer and use it in GitHub Desktop.
Visualization of Student Loan Complain dataset
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="vis.js"></script>
</head>
<style>
.axis text {
font: 10px sans-serif;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<div id="vis"></div>
<script>
var width = 800,
height = 400,
svg = d3.select("#vis").append("svg").attr("width",width).attr("height",height);
d3.csv("sloanComp.csv",function(_data){
var data = [];
_data.forEach(function(datum) {
var tmp = datum.date.split('-');
data.push({date: new Date(+tmp[1], +tmp[0]), count: +datum.count, category: datum.issue});
});
var vis = new dataVis();
svg.datum(data).call(vis.width(width).height(height));
});
</script>
</body>
</html>
date issue count
3-2012 D 1
3-2012 P 139
3-2012 G 14
3-2012 R 368
3-2012 C 2
4-2012 D 0
4-2012 P 66
4-2012 G 7
4-2012 R 177
4-2012 C 0
5-2012 D 0
5-2012 P 62
5-2012 G 3
5-2012 R 109
5-2012 C 0
6-2012 D 1
6-2012 P 146
6-2012 G 23
6-2012 R 400
6-2012 C 0
7-2012 D 0
7-2012 P 66
7-2012 G 6
7-2012 R 170
7-2012 C 0
8-2012 D 0
8-2012 P 69
8-2012 G 14
8-2012 R 135
8-2012 C 0
9-2012 D 0
9-2012 P 72
9-2012 G 8
9-2012 R 106
9-2012 C 0
10-2012 D 1
10-2012 P 89
10-2012 G 14
10-2012 R 162
10-2012 C 0
11-2012 D 0
11-2012 P 75
11-2012 G 7
11-2012 R 143
11-2012 C 0
12-2012 D 1
12-2012 P 56
12-2012 G 8
12-2012 R 122
12-2012 C 0
1-2013 D 0
1-2013 P 85
1-2013 G 15
1-2013 R 165
1-2013 C 0
2-2013 D 3
2-2013 P 73
2-2013 G 9
2-2013 R 149
2-2013 C 0
3-2013 D 0
3-2013 P 76
3-2013 G 3
3-2013 R 161
3-2013 C 0
4-2013 D 2
4-2013 P 63
4-2013 G 8
4-2013 R 163
4-2013 C 1
5-2013 D 1
5-2013 P 86
5-2013 G 10
5-2013 R 104
5-2013 C 0
6-2013 D 0
6-2013 P 70
6-2013 G 9
6-2013 R 104
6-2013 C 1
7-2013 D 0
7-2013 P 82
7-2013 G 12
7-2013 R 155
7-2013 C 0
8-2013 D 2
8-2013 P 74
8-2013 G 7
8-2013 R 177
8-2013 C 0
9-2013 D 1
9-2013 P 63
9-2013 G 6
9-2013 R 183
9-2013 C 0
10-2013 D 0
10-2013 P 67
10-2013 G 12
10-2013 R 231
10-2013 C 1
11-2013 D 1
11-2013 P 69
11-2013 G 5
11-2013 R 190
11-2013 C 3
12-2013 D 59
12-2013 P 50
12-2013 G 7
12-2013 R 149
12-2013 C 38
1-2014 D 187
1-2014 P 0
1-2014 G 6
1-2014 R 2
1-2014 C 159
2-2014 D 187
2-2014 P 1
2-2014 G 7
2-2014 R 1
2-2014 C 153
3-2014 D 208
3-2014 P 0
3-2014 G 7
3-2014 R 3
3-2014 C 185
4-2014 D 179
4-2014 P 0
4-2014 G 9
4-2014 R 0
4-2014 C 177
5-2014 D 184
5-2014 P 1
5-2014 G 4
5-2014 R 1
5-2014 C 157
6-2014 D 178
6-2014 P 0
6-2014 G 13
6-2014 R 0
6-2014 C 161
7-2014 D 192
7-2014 P 0
7-2014 G 2
7-2014 R 0
7-2014 C 162
8-2014 D 198
8-2014 P 0
8-2014 G 9
8-2014 R 0
8-2014 C 135
9-2014 D 189
9-2014 P 0
9-2014 G 20
9-2014 R 0
9-2014 C 155
10-2014 D 193
10-2014 P 0
10-2014 G 6
10-2014 R 0
10-2014 C 173
11-2014 D 179
11-2014 P 0
11-2014 G 7
11-2014 R 0
11-2014 C 155
12-2014 D 182
12-2014 P 0
12-2014 G 10
12-2014 R 1
12-2014 C 147
1-2015 D 205
1-2015 P 0
1-2015 G 12
1-2015 R 0
1-2015 C 137
2-2015 D 229
2-2015 P 0
2-2015 G 6
2-2015 R 0
2-2015 C 137
3-2015 D 277
3-2015 P 0
3-2015 G 12
3-2015 R 0
3-2015 C 143
4-2015 D 265
4-2015 P 0
4-2015 G 19
4-2015 R 0
4-2015 C 145
5-2015 D 242
5-2015 P 0
5-2015 G 13
5-2015 R 0
5-2015 C 129
6-2015 D 249
6-2015 P 0
6-2015 G 8
6-2015 R 0
6-2015 C 119
7-2015 D 231
7-2015 P 0
7-2015 G 11
7-2015 R 1
7-2015 C 168
8-2015 D 241
8-2015 P 0
8-2015 G 21
8-2015 R 0
8-2015 C 148
9-2015 D 260
9-2015 P 0
9-2015 G 12
9-2015 R 0
9-2015 C 127
10-2015 D 233
10-2015 P 0
10-2015 G 5
10-2015 R 0
10-2015 C 116
11-2015 D 170
11-2015 P 0
11-2015 G 9
11-2015 R 0
11-2015 C 107
12-2015 D 177
12-2015 P 0
12-2015 G 13
12-2015 R 0
12-2015 C 104
1-2016 D 220
1-2016 P 0
1-2016 G 13
1-2016 R 0
1-2016 C 100
2-2016 D 222
2-2016 P 0
2-2016 G 11
2-2016 R 0
2-2016 C 106
3-2016 D 244
3-2016 P 0
3-2016 G 7
3-2016 R 0
3-2016 C 108
4-2016 D 239
4-2016 P 0
4-2016 G 15
4-2016 R 0
4-2016 C 134
5-2016 D 12
5-2016 P 0
5-2016 G 0
5-2016 R 0
5-2016 C 2
function dataVis () {
var x = 0,
y = 0,
padding = { top: 40, bottom: 40, left: 20, right: 20},
width = 700,
height = 400;
function vis (selection) {
var data = selection.datum(),
timeScaleForAxis = d3.time.scale(), //Scale for just showing the x (time) axis
timeScale = d3.scale.ordinal(), //Actual scale to determine x coordinate
categScale = d3.scale.ordinal(),
categories = d3.set(data.map(function(d){ return d.category; })).values(),
colorScale = d3.scale.linear(),
dates = data.filter(function(d){
return d.category == categories[0];
}).map(function(d){ return d.date; }),
line = d3.svg.line(),
rateScale = d3.scale.linear(),
xAxis = d3.svg.axis(),
oldest = d3.min(dates),
newest = d3.max(dates),
countMax = d3.max(data.map(function(d){ return d.count; })),
borderWidth = 0.1,
monthText = ["January", "Febrary", "March", "April", "May", "June",
"July", "August", "September", "Octorber", "November", "December"],
/* Text is same as the original values */
categText = {
"D" : "Dealing with my lender or servicer",
"P" : "Problems when you are unable to pay",
"G" : "Getting a loan",
"R" : "Repaying your loan",
"C" : "Can't repay my loan"
},
//From ColorBrewer2.0
colors = ['#ffffff', '#ffffb2','#fed976','#feb24c','#fd8d3c','#f03b20','#bd0026'],
//Values are manually chose to show the data easier to see
colorDomain = [0, 1, 24, 90, 210, 278, countMax];
timeScale
.domain(dates)
.rangeRoundBands([padding.left, width - padding.right]);
timeScaleForAxis
.domain([oldest, newest])
.rangeRound([timeScale(oldest) + timeScale.rangeBand() / 2,
timeScale(newest) + timeScale.rangeBand() / 2]);
rateScale
.domain([0, countMax])
.range([0.0, 1.0]);
colorScale
.domain(colorDomain)
.range(colors);
categScale
.domain(categories)
.rangeRoundBands([padding.top, height - padding.bottom], 0.4);
xAxis.scale(timeScaleForAxis).orient("bottom");
line
.x(function(d){ return d; })
.y(function(d){ return d; })
.interpolate("linear-closed");
var gParent = selection.append("g"),
gColumn = gParent.append("g"),
gValues = gParent.append("g"),
gCateg = gParent.append("g"),
gLegend = gParent.append("g"),
columnIDPrefix = "_backColumn",
labelTxtIDPrefix = "_label",
monthTextID = "_monthText",
tooltipID = "_tooltip",
indicatorID = "_indicator",
opacityMOver = 0.2,
opacityDefault = 0,
lWidth = 120,
lHeight = 20,
lTextOfs = 4,
indicatorWidth = 5,
lx = (width - padding.left - padding.right) / 1.05 - lWidth,
ly = padding.top / 1.3,
getColumn = function (i) {
return d3.select("#" + columnIDPrefix + Math.floor(i / categories.length));
};
var mouseOverEvent = function (selection) {
selection.style("opacity", opacityMOver);
},
mouseLeaveEvent = function (selection) {
selection.style("opacity", opacityDefault);
d3.select("#" + monthTextID).remove();
};
//Draw Rects (Columns), which is highlighed when the mouse cursor is over
gColumn.selectAll("rect").data(data).enter().append("rect")
.attr("x", function(d) { return timeScale(d.date)})
.attr("y", padding.top)
.attr("id", function(d,i) { return columnIDPrefix + Math.floor(i / categories.length); })
.attr("height", height - (padding.top + padding.bottom))
.attr("width", timeScale.rangeBand())
.style({
"fill": "gray",
"opacity": opacityDefault
})
.on("mouseover", function() {
mouseOverEvent(d3.select(this));
})
.on("mouseleave", function() {
mouseLeaveEvent(d3.select(this));
});
//Draw Rects (Values)
gValues.selectAll("rect").data(data).enter().append("rect")
.attr("x", function(d) { return timeScale(d.date); })
.attr("y", function(d) { return categScale(d.category); })
.attr("height", categScale.rangeBand())
.attr("width", timeScale.rangeBand())
//.attr("width", dummyTimeScale.rangeBand())
.style({
"fill" : "white",
"stroke-width": borderWidth,
"stroke": "black",
"rx" : 0,
"ry" : 0
})
.on("mouseover", function(d, i) {
var rect = d3.select(this);
mouseOverEvent(getColumn(i));
d3.select(this).node().parentNode.appendChild(d3.select(this).node());
d3.select(this).style({ "stroke-width" : 1 });
d3.select("#" + labelTxtIDPrefix + d.category)
.transition().duration(100).style({
"font-size" : "17px"
});
//Add and show tooltip if not exist yet
if (gParent.select("#" + tooltipID).empty()) {
var gTooltip = gParent.append("g").attr("id", tooltipID),
rectX = +rect.attr("x"),
rectY = +rect.attr("y"),
rectH = +rect.attr("height"),
rectW = +rect.attr("width"),
ofs = 4,
tWidth = 130,
tHeight = 60,
//Top-left point of the tooltip
newX = rectX + rectW + ofs,
newY = rectY + rectH + ofs;
if (width - padding.left - padding.right < newX + tWidth)
newX = rectX - tWidth - ofs;
if (height < newY + tHeight)
newY = rectY - tHeight - ofs;
gTooltip.append("rect")
.attr("x", newX)
.attr("y", newY)
.attr("width", tWidth)
.attr("height", tHeight)
.style({
"fill": "white",
"stroke-width": 2,
"stroke": "black",
"rx": 3,
"ry": 3
});
//Add Date text to tooltip
gTooltip.append("text")
.attr("x", newX + tWidth / 2)
.attr("y", newY + tHeight / 3.2)
.text(function(){
var date = rect.datum().date;
return monthText[date.getMonth()] + ", " + date.getFullYear();
})
.style({
"font-family": "sans-serif",
"font-size": "13px",
"text-anchor": "middle"
});
//Add count text to tooltip
gTooltip.append("text")
.attr("x", newX + tWidth / 2)
.attr("y", newY + (tHeight - (tHeight / 3)))
.text(function(){
var count = +rect.datum().count;
return count + " issue" + ((count == 0 || count == 1)? "" : "s");
})
.style({
"font-family": "sans-serif",
"font-size": "17px",
"text-anchor": "middle"
});
}
d3.select("#" + indicatorID)
.attr("x", function(){
var count = +rect.datum().count;
return lx + (lWidth * rateScale(count)) - (indicatorWidth / 2);
})
.style("opacity", 1)
})
.on("mouseleave", function(d, i) {
mouseLeaveEvent(getColumn(i));
d3.select(this).style({ "stroke-width": 0.1 });
d3.select("#" + labelTxtIDPrefix + d.category)
.transition().duration(300).style({
"font-size" : "13px"
});
d3.select("#" + indicatorID).style("opacity", 0);
gParent.select("#" + tooltipID).remove();
});
//Animation for rect colors
gValues.selectAll("rect").transition().delay(function(d,i){ return i * 5; }).duration(800)
.style("fill", function(d){
return colorScale(d.count);
});
//Show category names
gCateg.selectAll("text").data(categories).enter().append("text")
.attr("x", padding.left)
.attr("y", function(d) { return categScale(d) - 4; })
.attr("id", function(d) { return labelTxtIDPrefix + d; })
//.transition().delay(1500)
.text(function(d) { return categText[d]; })
.style({
"font-size" : "13px",
"font-family" : "sans-serif"
});
//Show time axis at the bottom
gParent.append("g")
.attr("class","x axis")
.attr("transform", "translate(0," + (height - padding.bottom) + ")")
.call(xAxis);
//Color scheme for legend
var gradient = gLegend.append("defs")
.append("linearGradient")
.attr("id", "legendGradient")
.attr("x1", "0%")
.attr("y1", "100%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadmethond", "pad");
gradient.selectAll("stop").data(colorDomain).enter().append("stop")
.attr("offset", function(d){
return rateScale(d) * 100 + "%";
})
.attr("stop-color", function(d,i){
return colors[i];
})
.attr("stop-opacity", 1);
//Draw legend
gLegend.append("rect")
.attr("x", lx)
.attr("y", ly)
.transition()
.delay(1300)
.attr("width", lWidth)
.attr("height", lHeight)
.style({
"fill" : "url(#legendGradient)",
"stroke-width" : 0.3,
"stroke" : "black",
"rx" : 0.5,
"ry" : 0.5
});
//Min label for legend
gLegend.append("text")
.attr("x", lx)
.attr("y", ly - lTextOfs)
.transition()
.delay(1300)
.text("Min (0)")
.style({
"font-family" : "sans-serif",
"font-size" : "10px",
"text-anchor" : "middle"
});
//Max label for legend
gLegend.append("text")
.attr("x", lx + lWidth + lTextOfs)
.attr("y", ly - lTextOfs)
.transition()
.delay(1300)
.text("Max (" + countMax + ")")
.style({
"font-family" : "sans-serif",
"font-size" : "10px",
"text-anchor" : "middle"
});
//Draw Indicator, which shows the current value (count)
gLegend.append("rect")
.attr("x", lx + lWidth / 2)
.attr("y", ly)
.attr("id", indicatorID)
.attr("width", indicatorWidth)
.attr("height", lHeight - 1)
.style({
"fill" : "white",
"stroke" : "black",
"stroke-width" : 1.4,
"opacity" : 0
});
}
vis.x = function (val) {
x = val;
return vis;
};
vis.y = function (val) {
y = val;
return vis;
};
vis.width = function (val) {
width = val;
return vis;
};
vis.height = function (val) {
height = val;
return vis;
};
vis.padding = function (val) {
padding = val;
return vis;
};
return vis;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment