// taxr.js
// Sample JavaScript solution for Homework 5: Taxr
// INFO 343, Autumn 2012
// Morgan Doocy
(function() {
var WEB_SERVICE = 'taxr.php';
$.ajaxSetup({ error: ajaxError });
// When the DOM has finished loading, fetch and build the slider based on
// year data received from the web service.
$(document).ready(function() {
$.get(WEB_SERVICE, { format: 'json' }, function(years) {
var minyear = years[0].year;
var maxyear = years[years.length - 1].year;
// Initialize slider element and label the handle.
$('#curyear').text(minyear);
$('#slider').slider({
min: minyear,
max: maxyear,
step: 1,
slide: function(event, ui) {
$('#curyear').text(ui.value);
fetchData(ui.value);
}
});
$('#slider a').append($('#curyear').detach());
// Generate blue/red blackground "sparkline" chart, indicating the maximum
// tax rate and party of the president for each year (slider stop point).
var slider_width = parseInt($('#slider').css('width'));
var column_width = 1 / (years.length-1) * 100;
for (var i = 0; i < years.length; i++) {
var year = years[i].year;
var $col = $('<li>').css({
width: column_width + '%',
left: (column_width * i) + '%',
height: years[i].maxrate / 5,
'margin-left': (-(column_width) / 2) + '%'
}).addClass(years[i].party);
if (i == 0 || i == years.length - 1 || (year < 2010 && year % 10 == 0)) {
$('<label>').text(year).appendTo($col);
$('<hr>').addClass('rule').appendTo($col);
}
$col.appendTo('#slider');
}
// Hide loading gif and move into slider handle.
$('#loading').detach().appendTo('#slider a').hide();
});
// Re-fetch data when 'adjusted' checkbox selected/cleared.
$('#adjusted').change(function() {
fetchData($('#slider').slider('value'));
});
// Ensure details tooltip doesn't capture mouseover instead of rectangle behind it.
$('#details').mouseover(function() {
$(this).hide();
});
});
// Fetch tax data for the given year in JSON format.
function fetchData(year) {
$('#loading').show();
$.get(WEB_SERVICE, {
dollars: $('#adjusted').is(':checked') ? 'adjusted' : 'nominal',
format: 'json',
year: year
}, injectData);
}
// Inject the returned tax data into the page.
function injectData(data) {
$('#filing_statuses').empty();
// Establish maximum rate of all brackets this year.
var max = 0;
$.each(data.statuses, function(status, brackets) {
var highest = parseInt(brackets[brackets.length - 1].from);
max = highest > max ? highest : max;
});
// Create and inject a label for this status, and boxes for each bracket
// in this status.
$.each(data.statuses, function(status, brackets) {
$('<dt>').text(status).appendTo('#filing_statuses');
var $dd = $('<dd>');
var $ul = $('<ul>');
$.each(brackets, function(i, bracket) {
var height = bracket.rate;
var left = parseInt((bracket.from) / 2000);
var width;
if (i == brackets.length - 1) {
width = parseInt((max - bracket.from) / 2000);
width = (left + width) < 1000 ? 1000 - left : width;
} else {
width = parseInt((bracket.to - bracket.from) / 2000);
}
// console.log("Bracket for " + bracket.rate + "% is " + height + "px tall, " + width + "px (= " + bracket.to + " - " + bracket.from + " / " + max + ") wide, and " + left + "px from the left");
var $rate = $('<label>').addClass('rate').text(bracket.rate);
var $from = $('<label>').addClass('from').text(parseInt(bracket.from / 1000));
$('<li>').mousemove(bracketMove).mouseover(bracketOver).mouseout(bracketOut).css({
'height': height,
'width': width,
'left': left
}).append($rate).append($from).appendTo($ul);
});
$ul.appendTo($dd.appendTo('#filing_statuses'));
});
$('#loading').hide();
}
// Update the details tooltip with correct information based on the cursor
// location inside the hovered bracket.
function bracketMove(event) {
var $target = $(event.target);
if (event.target.nodeName == 'LI' && !$target.hasClass('current')) {
var offsetParent = $target.parent().offset();
var x = event.pageX;
var y = event.pageY;
var income = parseInt(x - offsetParent.left) * 2000;
var rate = $target.find('label.rate').text();
// console.log("income: " + income + " rate: " + rate + "%");
var taxburden = 0;
$target.prevAll().each(function(i, elem) {
var $elem = $(elem);
var rate = $elem.find('label.rate').text();
var subincome = $elem.next().find('label.from').text() - $elem.find('label.from').text();
// console.log("subincome: " + (subincome * 1000) + " rate: " + rate + "%");
taxburden += (rate / 100) * (subincome * 1000);
});
taxburden += (rate / 100) * (income - ($target.find('label.from').text() * 1000));
// console.log("remainder: " + (income - ($target.find('label.from').text() * 1000)));
taxburden = Math.round(taxburden);
$('#income').text('$' + commaSeparateNumber(income));
$('#marginaltax').text(rate + '%');
$('#taxburden').text('$' + commaSeparateNumber(taxburden));
$('#averagetax').text(Math.round(taxburden / income * 100) + '%');
$('#details').css({
'left': x + 10 + 'px',
'top': y + 10 + 'px'
}).show();
}
}
// Return the given integer formatted as comma-separated groups of three digits.
function commaSeparateNumber(val){
while (/(\d+)(\d{3})/.test(val.toString())){
val = val.toString().replace(/(\d+)(\d{3})/, '$1'+','+'$2');
}
return val;
}
// Highlight this and all previous brackets when moused over.
function bracketOver(event) {
if (event.target.nodeName == 'LI') {
$(this).prevAll().andSelf().addClass('over');
}
}
// Unhighlight all brackets in this filing status when moused out.
function bracketOut(event) {
if (event.target.nodeName == 'LI') {
$(this).prevAll().andSelf().removeClass('over');
$('#details').hide();
}
}
// Display a useful error message on Ajax failure.
function ajaxError(jqxhr, type, error) {
var msg = "An Ajax error occurred!\n\n";
if (type == 'error') {
if (jqxhr.readyState == 0) {
// Request was never made - security block?
msg += "Looks like the browser security-blocked the request.";
} else {
// Probably an HTTP error.
msg += 'Error code: ' + jqxhr.status + "\n" +
'Error text: ' + error + "\n" +
'Full content of response: \n\n' + jqxhr.responseText;
}
} else {
msg += 'Error type: ' + type;
if (error != "") {
msg += "\nError text: " + error;
}
}
alert(msg);
}
})();