diff --git a/public/js/calculator.js b/public/js/calculator.js new file mode 100644 index 0000000000..bb92d6a65b --- /dev/null +++ b/public/js/calculator.js @@ -0,0 +1,268 @@ +$(document).ready(function () { + var bootcamps = '' + $.getJSON('/coding-bootcamp-cost-calculator.json', function(data) { + bootcamps = data; + }); + var city = ""; + $("body").data("state", "stacked"); + $('#city-buttons').on("click", "button", function () { + $(this).addClass('animated pulse'); + city = $(this).attr("id"); + $('#chosen').text('Coming from ' + city.replace(/-/g, ' ').replace(/\w\S*/g, function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }) + ', and making $_______, your true costs will be:'); + setTimeout(function () { + $('#city-buttons').hide(); + $('#income').addClass('animated fadeIn').show(); + }, 1000); + }); + $('#income').on("click", "button", function () { + $(this).addClass('animated pulse'); + setTimeout(function () { + $('#income').hide(); + $('#chart').show(); + $('#chart-controls').addClass('animated fadeIn').show(); + $('#explanation').addClass('animated fadeIn').show(); + }, 1000); + var lastYearsIncome = parseInt($(this).attr("id")); + $('#chosen').text('Coming from ' + city.replace(/-/g, ' ').replace(/\w\S*/g, function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }) + ', and making $' + lastYearsIncome.toString().replace(/0000$/, '0,000') + ', your true costs will be:'); + var categoryNames = ['Opportunity Cost at Current Wage', 'Financing Cost', 'Housing Cost', 'Tuition / Wage Garnishing']; + bootcamps.forEach(function (camp) { + var x0 = 0; + if (camp.cities.indexOf(city) > -1) { + weeklyHousing = 0; + } else { + weeklyHousing = +camp.housing; + } + camp.mapping = [{ + name: camp.name, + label: 'Tuition / Wage Garnishing', + value: +camp.cost, + x0: x0, + x1: x0 += +camp.cost + }, { + name: camp.name, + label: 'Financing Cost', + value: +Math.floor(camp.cost * .09519), + x0: +camp.cost, + x1: camp.finance ? x0 += +Math.floor(camp.cost * .09519) : 0 + }, { + name: camp.name, + label: 'Housing Cost', + value: +weeklyHousing * camp.weeks, + x0: camp.finance ? +Math.floor(camp.cost * 1.09519) : camp.cost, + x1: x0 += weeklyHousing * camp.weeks + }, { + name: camp.name, + label: 'Opportunity Cost at Current Wage', + value: +(Math.floor(camp.weeks * lastYearsIncome / 50)), + x0: camp.finance ? +(Math.floor(camp.cost * 1.09519) + weeklyHousing * camp.weeks) : +camp.cost + weeklyHousing * camp.weeks, + x1: x0 += +(Math.floor(camp.weeks * lastYearsIncome / 50)) + }]; + camp.total = camp.mapping[camp.mapping.length - 1].x1; + }); + bootcamps.sort(function (a, b) { + return a.total - b.total; + }); + maxValue = 0; + bootcamps.forEach(function (camp) { + camp.mapping.forEach(function (elem) { + if (elem.value > maxValue) { + maxValue = elem.value; + } + }); + }); + var xStackMax = d3.max(bootcamps, function (d) { + return d.total; + }), //Scale for Stacked + xGroupMax = bootcamps.map(function (camp) { + return camp.mapping.reduce(function (a, b) { + return a.value > b.value ? a.value : b.value; + }); + }).reduce(function (a, b) { + return a > b ? a : b; + }); + var margin = { + top: 30, + right: 60, + bottom: 50, + left: 140 + }, + width = 800 - margin.left - margin.right, + height = 1200 - margin.top - margin.bottom; + var barHeight = 20; + var xScale = d3.scale.linear() + .domain([0, xStackMax]) + .rangeRound([0, width]); + var y0Scale = d3.scale.ordinal() + .domain(bootcamps.map(function (d) { + return d.name; + })) + .rangeRoundBands([0, height], .1); + var y1Scale = d3.scale.ordinal() + .domain(categoryNames).rangeRoundBands([0, y0Scale.rangeBand()]); + var color = d3.scale.ordinal() + .range(["#215f1e", "#5f5c1e", "#1e215f", "#5c1e5f"]) + .domain(categoryNames); + var svg = d3.select("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + var selection = svg.selectAll(".series") + .data(bootcamps) + .enter().append("g") + .attr("class", "series") + .attr("transform", function (d) { + return "translate(0," + y0Scale(d.name) + ")"; + }); + var rect = selection.selectAll("rect") + .data(function (d) { + return d.mapping; + }) + .enter().append("rect") + .attr("x", 0) + .attr("width", 0) + .attr("height", y0Scale.rangeBand()) + .style("fill", function (d) { + return color(d.label); + }) + .style("stroke", "white") + .on("mouseover", function (d) { + showPopover.call(this, d); + }) + .on("mouseout", function (d) { + removePopovers(); + }); + rect.transition() + .delay(function (d, i) { + return i * 10; + }) + .attr("x", function (d) { + return xScale(d.x0); + }) + .attr("width", function (d) { + return xScale((d.x1) - (d.x0)); + }); + d3.selectAll("#transform").on("click", function () { + $('#transform').addClass('animated pulse'); + change(); + setTimeout(function () { + $('#transform').removeClass('animated pulse'); + }, 1000); + }); + + function change() { + if ($("body").data("state") === "stacked") { + transitionGrouped(); + $("body").data("state", "grouped"); + } else { + transitionStacked(); + $("body").data("state", "stacked"); + } + } + + function transitionGrouped() { + xScale.domain = ([0, xGroupMax]); + rect.transition() + .duration(500) + .delay(function (d, i) { + return i * 10; + }) + .attr("width", function (d) { + return xScale((d.x1) - (d.x0)); + }) + .transition() + .attr("y", function (d) { + return y1Scale(d.label); + }) + .attr("x", 0) + .attr("height", y1Scale.rangeBand()) + } + + function transitionStacked() { + xScale.domain = ([0, xStackMax]); + rect.transition() + .duration(500) + .delay(function (d, i) { + return i * 10; + }) + .attr("x", function (d) { + return xScale(d.x0); + }) + .transition() + .attr("y", function (d) { + return y0Scale(d.label); + }) + .attr("height", y0Scale.rangeBand()) + } + + //axes + var xAxis = d3.svg.axis() + .scale(xScale) + .orient("bottom"); + var yAxis = d3.svg.axis() + .scale(y0Scale) + .orient("left"); + svg.append("g") + .attr("class", "y axis") + .call(yAxis); + svg.append("g") + .attr("class", "x axis") + .attr("transform", "translate(0," + height + ")") + .call(xAxis) + .append("text") + .attr("x", 300) + .attr("y", 35) + .attr("dy", ".35em") + .style("text-anchor", "middle") + .text("Cost in $USD"); + //tooltips + function removePopovers() { + $('.popover').each(function () { + $(this).remove(); + }); + } + + function showPopover(d) { + $(this).popover({ + title: d.name, + placement: 'auto top', + container: 'body', + trigger: 'manual', + html: true, + content: function () { + return d.label + + "
$" + + d3.format(",")(d.value ? d.value : d.x1 - d.x0); + } + }); + $(this).popover('show') + } + + //legends + var legend = svg.selectAll(".legend") + .data(categoryNames.slice().reverse()) + .enter().append("g") + .attr("class", "legend") + .attr("transform", function (d, i) { + return "translate(30," + i * y0Scale.rangeBand() * 1.1 + ")"; + }); + legend.append("rect") + .attr("x", width - y0Scale.rangeBand()) + .attr("width", y0Scale.rangeBand()) + .attr("height", y0Scale.rangeBand()) + .style("fill", color) + .style("stroke", "white"); + legend.append("text") + .attr("x", width - y0Scale.rangeBand() * 1.2) + .attr("y", 12) + .attr("dy", ".35em") + .style("text-anchor", "end") + .text(function (d) { + return d; + }); + }); +}); diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index b44226f82a..7dd89b1b4f 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -28,7 +28,7 @@ module.exports = function(app) { router.post('/get-pair', getPair); router.get('/chat', chat); router.get('/coding-bootcamp-cost-calculator', bootcampCalculator); - router.get('/bootcamp-calculator.json', bootcampCalculatorJson); + router.get('/coding-bootcamp-cost-calculator.json', bootcampCalculatorJson); router.get('/twitch', twitch); router.get('/pmi-acp-agile-project-managers', agileProjectManagers); router.get('/pmi-acp-agile-project-managers-form', agileProjectManagersForm); diff --git a/server/views/resources/calculator.jade b/server/views/resources/calculator.jade index 3dfc658b1f..8ae888041b 100644 --- a/server/views/resources/calculator.jade +++ b/server/views/resources/calculator.jade @@ -1,16 +1,12 @@ extends ../layout block content - link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/css/bootstrap2/bootstrap-switch.min.css') - script(src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/js/bootstrap-switch.min.js") + script(src="../../../js/calculator.js") .panel.panel-info .panel-heading.text-center Coding Bootcamp Cost Calculator .panel-body .row .col-xs-12.col-sm-10.col-sm-offset-1 h3.text-primary#chosen Coming from _______, and making $_______, your true costs will be: - #chart-controls.initially-hidden - .text-center - button#transform.btn.btn-primary.btn-lg.animated Transform #city-buttons h2 Where do you live? .col-xs-12.col-sm-6.col-md-4.btn-nav @@ -51,6 +47,7 @@ block content button#toronto.btn.btn-primary.btn-block.btn-lg Toronto .col-xs-12.btn-nav button#other.btn.btn-primary.btn-block.btn-lg Other + .spacer #income.initially-hidden h2 How much money did you make last year (in USD)? .col-xs-12.col-sm-6.col-md-4.btn-nav @@ -83,272 +80,16 @@ block content button#180000.btn.btn-primary.btn-block.btn-lg(href='#') $180,000 .col-xs-12.col-sm-6.col-md-4.btn-nav button#200000.btn.btn-primary.btn-block.btn-lg(href='#') $200,000 + .spacer #chart.initially-hidden .d3-centered svg.chart #explanation.initially-hidden .text-center + .text-center + button#transform.btn.btn-primary.btn-block.btn-lg Transform + .button-spacer a(href='/coding-bootcamp-cost-calculator.json') View Data Source JSON span   •   a(href='/coding-bootcamp-cost-calculator') Recalculate p.large-p Test - script. - $(document).ready(function () { - var bootcamps = !{JSON.stringify(bootcampJson)}; - var city = ""; - $( "body" ).data( "state", "stacked"); - $('#city-buttons').on("click", "button", function () { - $(this).addClass('animated pulse'); - setTimeout(function () { - $('#city-buttons').hide(); - $('#chosen').text('Coming from ' + city.replace(/-/g, ' ').replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();})); - $('#income').addClass('animated fadeIn').show(); - }, 1000); - city = $(this).attr("id"); - }); - $('#income').on("click", "button", function() { - $(this).addClass('animated pulse'); - setTimeout(function () { - $('#income').hide(); - $('#chart').show(); - $('#chart-controls').addClass('animated fadeIn').show(); - $('#explanation').addClass('animated fadeIn').show(); - }, 1000); - var lastYearsIncome = parseInt($(this).attr("id")); - $('#chosen').append(', and making $' + lastYearsIncome.toString().replace(/0000/, '0,000') + ', your true costs will be:'); - var categoryNames = ['Opportunity Cost at Current Wage', 'Financing Cost', 'Housing Cost', 'Tuition / Wage Garnishing']; - bootcamps.forEach(function (camp) { - var x0 = 0; - if (camp.cities.indexOf(city) > -1) { - weeklyHousing = 0; - } else { - weeklyHousing = +camp.housing; - } - camp.mapping = [{ - name: camp.name, - label: 'Tuition / Wage Garnishing', - value: +camp.cost, - x0: x0, - x1: x0 += +camp.cost - }, { - name: camp.name, - label: 'Financing Cost', - value: +Math.floor(camp.cost * .09519), - x0: +camp.cost, - x1: camp.finance ? x0 += +Math.floor(camp.cost * .09519) : 0 - }, { - name: camp.name, - label: 'Housing Cost', - value: +weeklyHousing * camp.weeks, - x0: camp.finance ? +Math.floor(camp.cost * 1.09519) : camp.cost, - x1: x0 += weeklyHousing * camp.weeks - }, { - name: camp.name, - label: 'Opportunity Cost at Current Wage', - value: +(Math.floor(camp.weeks * lastYearsIncome / 50)), - x0: camp.finance ? +(Math.floor(camp.cost * 1.09519) + weeklyHousing * camp.weeks) : +camp.cost + weeklyHousing * camp.weeks, - x1: x0 += +(Math.floor(camp.weeks * lastYearsIncome / 50)) - }]; - camp.total = camp.mapping[camp.mapping.length - 1].x1; - }); - bootcamps.sort(function(a, b) { return a.total - b.total; }); - maxValue = 0; - bootcamps.forEach(function (camp) { - camp.mapping.forEach(function (elem) { - if (elem.value > maxValue) { - maxValue = elem.value; - } - }); - }); - var xStackMax = d3.max(bootcamps, function (d) { - return d.total; - }), //Scale for Stacked - xGroupMax = bootcamps.map(function (camp) { - return camp.mapping.reduce(function (a, b) { - return a.value > b.value ? a.value : b.value; - }); - }).reduce(function (a, b) { - return a > b ? a : b; - }); - var margin = { - top: 30, - right: 60, - bottom: 50, - left: 140 - }, - width = 800 - margin.left - margin.right, - height = 1200 - margin.top - margin.bottom; - var barHeight = 20; - var xScale = d3.scale.linear() - .domain([0, xStackMax]) - .rangeRound([0, width]); - var y0Scale = d3.scale.ordinal() - .domain(bootcamps.map(function (d) { - return d.name; - })) - .rangeRoundBands([0, height], .1); - var y1Scale = d3.scale.ordinal() - .domain(categoryNames).rangeRoundBands([0, y0Scale.rangeBand()]); - var color = d3.scale.ordinal() - .range(["#215f1e", "#5f5c1e", "#1e215f", "#5c1e5f"]) - .domain(categoryNames); - var svg = d3.select("svg") - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - var selection = svg.selectAll(".series") - .data(bootcamps) - .enter().append("g") - .attr("class", "series") - .attr("transform", function (d) { - return "translate(0," + y0Scale(d.name) + ")"; - }); - var rect = selection.selectAll("rect") - .data(function (d) { - return d.mapping; - }) - .enter().append("rect") - .attr("x", 0) - .attr("width", 0) - .attr("height", y0Scale.rangeBand()) - .style("fill", function (d) { - return color(d.label); - }) - .style("stroke", "white") - .on("mouseover", function (d) { - showPopover.call(this, d); - }) - .on("mouseout", function (d) { - removePopovers(); - }); - rect.transition() - .delay(function (d, i) { - return i * 10; - }) - .attr("x", function (d) { - return xScale(d.x0); - }) - .attr("width", function (d) { - return xScale((d.x1) - (d.x0)); - }); - d3.selectAll("#transform").on("click", function() { - $('#transform').addClass('pulse'); - change(); - setTimeout( function() { - $('#transform').removeClass('pulse'); - }, 1000); - }); - - function change() { - if ($("body").data("state") === "stacked") { - transitionGrouped(); - $("body").data("state", "grouped"); - } else { - transitionStacked(); - $("body").data("state", "stacked"); - } - } - - function transitionGrouped() { - xScale.domain = ([0, xGroupMax]); - rect.transition() - .duration(500) - .delay(function (d, i) { - return i * 10; - }) - .attr("width", function (d) { - return xScale((d.x1) - (d.x0)); - }) - .transition() - .attr("y", function (d) { - return y1Scale(d.label); - }) - .attr("x", 0) - .attr("height", y1Scale.rangeBand()) - } - - function transitionStacked() { - xScale.domain = ([0, xStackMax]); - rect.transition() - .duration(500) - .delay(function (d, i) { - return i * 10; - }) - .attr("x", function (d) { - return xScale(d.x0); - }) - .transition() - .attr("y", function (d) { - return y0Scale(d.label); - }) - .attr("height", y0Scale.rangeBand()) - } - - //axes - var xAxis = d3.svg.axis() - .scale(xScale) - .orient("bottom"); - var yAxis = d3.svg.axis() - .scale(y0Scale) - .orient("left"); - svg.append("g") - .attr("class", "y axis") - .call(yAxis); - svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + height + ")") - .call(xAxis) - .append("text") - .attr("x", 300) - .attr("y", 35) - .attr("dy", ".35em") - .style("text-anchor", "middle") - .text("Cost in $USD"); - //tooltips - function removePopovers() { - $('.popover').each(function () { - $(this).remove(); - }); - } - - function showPopover(d) { - $(this).popover({ - title: d.name, - placement: 'auto top', - container: 'body', - trigger: 'manual', - html: true, - content: function () { - return d.label + - "
$" + - d3.format(",")(d.value ? d.value : d.x1 - d.x0); - } - }); - $(this).popover('show') - } - - //legends - var legend = svg.selectAll(".legend") - .data(categoryNames.slice().reverse()) - .enter().append("g") - .attr("class", "legend") - .attr("transform", function (d, i) { - return "translate(30," + i * y0Scale.rangeBand() * 1.1 + ")"; - }); - legend.append("rect") - .attr("x", width - y0Scale.rangeBand()) - .attr("width", y0Scale.rangeBand()) - .attr("height", y0Scale.rangeBand()) - .style("fill", color) - .style("stroke", "white"); - legend.append("text") - .attr("x", width - y0Scale.rangeBand() * 1.2) - .attr("y", 12) - .attr("dy", ".35em") - .style("text-anchor", "end") - .text(function (d) { - return d; - }); - }); - });