From 2cb0a3d717edbb2dd88b6e20c91ce0f2315476e4 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Tue, 16 Jun 2015 12:32:12 -0700 Subject: [PATCH 1/4] futher improve calculator ux --- gulpfile.js | 8 ++- public/css/main.less | 12 +--- server/boot/randomAPIs.js | 2 +- server/views/resources/calculator.jade | 88 ++++++++++++++------------ 4 files changed, 59 insertions(+), 51 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 51678484e09..46c6cbd82d1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -70,7 +70,7 @@ gulp.task('lint', function() { .pipe(eslint.format()); }); -gulp.task('build', function() { +gulp.task('less', function() { return gulp.src('./public/css/*.less') .pipe(less({ paths: [ path.join(__dirname, 'less', 'includes') ] @@ -78,4 +78,8 @@ gulp.task('build', function() { .pipe(gulp.dest('./public/css/')); }); -gulp.task('default', ['build', 'serve', 'sync']); +gulp.task('watch', ['less', 'serve', 'sync'], function() { + gulp.watch('./public/css/*.less', ['less']); +}); + +gulp.task('default', ['less', 'serve', 'sync', 'watch']); diff --git a/public/css/main.less b/public/css/main.less index 424ae26933c..aa6dc28c0cd 100644 --- a/public/css/main.less +++ b/public/css/main.less @@ -1123,13 +1123,8 @@ hr { // Calculator styles -.hidden-initially { - visibility: hidden; -} - -#four p{ - font-size: .6em; - color: black; +.initially-hidden { + display: none; } .chart rect { @@ -1137,8 +1132,7 @@ hr { } .chart text { - fill: #121401; - font: 13px sans-serif; + font-size: 14px; text-anchor: end; } diff --git a/server/boot/randomAPIs.js b/server/boot/randomAPIs.js index 8836f4f638e..b44226f82a1 100644 --- a/server/boot/randomAPIs.js +++ b/server/boot/randomAPIs.js @@ -27,7 +27,7 @@ module.exports = function(app) { router.post('/get-help', getHelp); router.post('/get-pair', getPair); router.get('/chat', chat); - router.get('/bootcamp-calculator', bootcampCalculator); + router.get('/coding-bootcamp-cost-calculator', bootcampCalculator); router.get('/bootcamp-calculator.json', bootcampCalculatorJson); router.get('/twitch', twitch); router.get('/pmi-acp-agile-project-managers', agileProjectManagers); diff --git a/server/views/resources/calculator.jade b/server/views/resources/calculator.jade index 373100da77b..3dfc658b1fd 100644 --- a/server/views/resources/calculator.jade +++ b/server/views/resources/calculator.jade @@ -1,21 +1,16 @@ 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") .panel.panel-info .panel-heading.text-center Coding Bootcamp Cost Calculator .panel-body .row .col-xs-12.col-sm-10.col-sm-offset-1 - h2.text-primary#chosen + h3.text-primary#chosen Coming from _______, and making $_______, your true costs will be: #chart-controls.initially-hidden - form - label - input(type='radio', name='mode', value='grouped') - |   Grouped   - label - input(type='radio', name='mode', value='stacked') - |   Stacked - br - a(href='/bootcamp-calculator.json') View Data Source JSON + .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 @@ -56,7 +51,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 - #income.hidden-by-default + #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 button#0.btn.btn-primary.btn-block.btn-lg(href='#') $0 @@ -88,25 +83,39 @@ 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 - #chart.hidden-by-default - svg.chart - + #chart.initially-hidden + .d3-centered + svg.chart + #explanation.initially-hidden + .text-center + 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 = ""; - var cityArray = ["san-fransisco", "los-angeles", "chicago", "austin", "new-york-city", "melbourne", "hong-kong", "seattle", "singapore", "london", "toronto", "portland", "brisbane", "atlanta", "raleigh-durham"]; + $( "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"); - $('#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').css({visibility: 'visible'}); }); $('#income').on("click", "button", function() { - $('#income').hide(); - $('#chart').css({visibility: 'visible'}); + $(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(' making $' + lastYearsIncome.toString().replace(/0000/, '0,000') + ', your true costs are:'); + $('#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; @@ -117,7 +126,7 @@ block content } camp.mapping = [{ name: camp.name, - label: 'Opportunity Cost at Current Wage', + label: 'Tuition / Wage Garnishing', value: +camp.cost, x0: x0, x1: x0 += +camp.cost @@ -135,7 +144,7 @@ block content x1: x0 += weeklyHousing * camp.weeks }, { name: camp.name, - label: 'Tuition / Wage Garnishing', + 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)) @@ -145,11 +154,9 @@ block content bootcamps.sort(function(a, b) { return a.total - b.total; }); maxValue = 0; bootcamps.forEach(function (camp) { - camp.mapping.forEach(function (thing) { - //console.log(thing.value ); - if (thing.value > maxValue) { - maxValue = thing.value; - console.log(maxValue); + camp.mapping.forEach(function (elem) { + if (elem.value > maxValue) { + maxValue = elem.value; } }); }); @@ -225,19 +232,22 @@ block content .attr("width", function (d) { return xScale((d.x1) - (d.x0)); }); - d3.selectAll("input").on("change", change); - var timeout= setTimeout(function () { - d3.select("input[value=\"stacked\"]").property("checked",true).each(change); - // d3.select("input[value=\"stacked\"]").property("checked",true).each(change); - }, 4000); - var timeout= setTimeout(function () { - d3.select("input[value=\"grouped\"]").property("checked",true).each(change); - }, 1500); + d3.selectAll("#transform").on("click", function() { + $('#transform').addClass('pulse'); + change(); + setTimeout( function() { + $('#transform').removeClass('pulse'); + }, 1000); + }); function change() { - clearTimeout(timeout); - if (this.value === "grouped") transitionGrouped(); - else transitionStacked(); + if ($("body").data("state") === "stacked") { + transitionGrouped(); + $("body").data("state", "grouped"); + } else { + transitionStacked(); + $("body").data("state", "stacked"); + } } function transitionGrouped() { From 40b5b05ea4a7a7f01577a69254249b2cdccaf6d3 Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Tue, 16 Jun 2015 13:03:13 -0700 Subject: [PATCH 2/4] factor JavaScript and JSON into separate files --- public/js/calculator.js | 268 ++++++++++++++++++++++++ server/boot/randomAPIs.js | 2 +- server/views/resources/calculator.jade | 271 +------------------------ 3 files changed, 275 insertions(+), 266 deletions(-) create mode 100644 public/js/calculator.js diff --git a/public/js/calculator.js b/public/js/calculator.js new file mode 100644 index 00000000000..bb92d6a65b4 --- /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 b44226f82a1..7dd89b1b4fd 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 3dfc658b1fd..8ae888041b2 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; - }); - }); - }); From 2e1babb12dc7618a03971cfea96be71f7659d51a Mon Sep 17 00:00:00 2001 From: Quincy Larson Date: Tue, 16 Jun 2015 13:46:24 -0700 Subject: [PATCH 3/4] finalize calculator for release --- public/js/calculator.js | 6 +- server/views/partials/footer.jade | 3 - server/views/resources/calculator.jade | 193 +++++++++++++------------ 3 files changed, 104 insertions(+), 98 deletions(-) diff --git a/public/js/calculator.js b/public/js/calculator.js index bb92d6a65b4..8786b78bea2 100644 --- a/public/js/calculator.js +++ b/public/js/calculator.js @@ -20,7 +20,7 @@ $(document).ready(function () { $(this).addClass('animated pulse'); setTimeout(function () { $('#income').hide(); - $('#chart').show(); + $('#chart').addClass('animated fadeIn').show(); $('#chart-controls').addClass('animated fadeIn').show(); $('#explanation').addClass('animated fadeIn').show(); }, 1000); @@ -28,7 +28,7 @@ $(document).ready(function () { $('#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']; + var categoryNames = ['Lost Wages', 'Financing Cost', 'Housing Cost', 'Tuition / Wage Garnishing']; bootcamps.forEach(function (camp) { var x0 = 0; if (camp.cities.indexOf(city) > -1) { @@ -56,7 +56,7 @@ $(document).ready(function () { x1: x0 += weeklyHousing * camp.weeks }, { name: camp.name, - label: 'Opportunity Cost at Current Wage', + label: 'Lost Wages', 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)) diff --git a/server/views/partials/footer.jade b/server/views/partials/footer.jade index 0af2fce328f..fe25c7723a8 100644 --- a/server/views/partials/footer.jade +++ b/server/views/partials/footer.jade @@ -7,7 +7,6 @@ a.ion-social-facebook(href="/field-guide/how-can-i-find-other-free-code-camp-campers-in-my-city")  Facebook   a.ion-social-twitter(href="http://twitter.com/freecodecamp", target='_blank')  Twitter   a.ion-locked(href="/privacy")  Privacy   - a.ion-android-mail(href="mailto:team@freecodecamp.com")  Contact   .col-xs-12.visible-xs.visible-sm a.ion-speakerphone(href='http://blog.freecodecamp.com', target='_blank') span.sr-only Free Code Camp's Blog @@ -23,5 +22,3 @@ span.sr-only Free Code Camp on Twitter a.ion-locked(href="/privacy") span.sr-only Free Code Camp's Privacy Policy - a.ion-android-mail(href="mailto:team@freecodecamp.com") - span.sr-only Contact Free Code Camp by email diff --git a/server/views/resources/calculator.jade b/server/views/resources/calculator.jade index 8ae888041b2..5e253521396 100644 --- a/server/views/resources/calculator.jade +++ b/server/views/resources/calculator.jade @@ -1,95 +1,104 @@ -extends ../layout +extends ../layout-wide block content script(src="../../../js/calculator.js") - .panel.panel-info - .panel-heading.text-center Coding Bootcamp Cost Calculator - .panel-body - .row + .row + .col-xs-12.col-sm-10.col-md-8.col-lg-6.col-sm-offset-1.col-md-offset-2.col-lg-offset-3 + h3.text-center.text-primary#chosen Coming from _______, and making $_______, your true costs will be: + #city-buttons + .spacer + h2.text-center Where do you live? + .spacer + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#atlanta.btn.btn-primary.btn-block.btn-lg Atlanta + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#austin.btn.btn-primary.btn-block.btn-lg Austin + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#brisbane.btn.btn-primary.btn-block.btn-lg Brisbane + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#boulder.btn.btn-primary.btn-block.btn-lg Boulder + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#chicago.btn.btn-primary.btn-block.btn-lg Chicago + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#denver.btn.btn-primary.btn-block.btn-lg Denver + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#hong-kong.btn.btn-primary.btn-block.btn-lg Hong Kong + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#london.btn.btn-primary.btn-block.btn-lg London + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#los-angeles.btn.btn-primary.btn-block.btn-lg Los Angeles + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#manchester.btn.btn-primary.btn-block.btn-lg Manchester + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#melbourne.btn.btn-primary.btn-block.btn-lg Melbourne + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#new-york-city.btn.btn-primary.btn-block.btn-lg New York City + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#portland.btn.btn-primary.btn-block.btn-lg Portland + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#raleigh-durham.btn.btn-primary.btn-block.btn-lg Raleigh-Durham + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#san-francisco.btn.btn-primary.btn-block.btn-lg San Fransisco + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#seattle.btn.btn-primary.btn-block.btn-lg Seattle + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#singapore.btn.btn-primary.btn-block.btn-lg Singapore + .col-xs-12.col-sm-12.col-md-4.btn-nav + 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 + .spacer + h2.text-center How much money did you make last year (in USD)? + .spacer + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#0.btn.btn-primary.btn-block.btn-lg(href='#') $0 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#20000.btn.btn-primary.btn-block.btn-lg(href='#') $20,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#30000.btn.btn-primary.btn-block.btn-lg(href='#') $30,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#40000.btn.btn-primary.btn-block.btn-lg(href='#') $40,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#50000.btn.btn-primary.btn-block.btn-lg(href='#') $50,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#60000.btn.btn-primary.btn-block.btn-lg(href='#') $60,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#70000.btn.btn-primary.btn-block.btn-lg(href='#') $70,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#80000.btn.btn-primary.btn-block.btn-lg(href='#') $80,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#90000.btn.btn-primary.btn-block.btn-lg(href='#') $90,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#100000.btn.btn-primary.btn-block.btn-lg(href='#') $100,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#120000.btn.btn-primary.btn-block.btn-lg(href='#') $120,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#140000.btn.btn-primary.btn-block.btn-lg(href='#') $140,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#160000.btn.btn-primary.btn-block.btn-lg(href='#') $160,000 + .col-xs-12.col-sm-12.col-md-4.btn-nav + button#180000.btn.btn-primary.btn-block.btn-lg(href='#') $180,000 + .col-xs-12.col-sm-12.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 .col-xs-12.col-sm-10.col-sm-offset-1 - h3.text-primary#chosen Coming from _______, and making $_______, your true costs will be: - #city-buttons - h2 Where do you live? - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#atlanta.btn.btn-primary.btn-block.btn-lg Atlanta - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#austin.btn.btn-primary.btn-block.btn-lg Austin - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#brisbane.btn.btn-primary.btn-block.btn-lg Brisbane - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#boulder.btn.btn-primary.btn-block.btn-lg Boulder - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#chicago.btn.btn-primary.btn-block.btn-lg Chicago - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#denver.btn.btn-primary.btn-block.btn-lg Denver - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#hong-kong.btn.btn-primary.btn-block.btn-lg Hong Kong - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#london.btn.btn-primary.btn-block.btn-lg London - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#los-angeles.btn.btn-primary.btn-block.btn-lg Los Angeles - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#manchester.btn.btn-primary.btn-block.btn-lg Manchester - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#melbourne.btn.btn-primary.btn-block.btn-lg Melbourne - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#new-york-city.btn.btn-primary.btn-block.btn-lg New York City - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#portland.btn.btn-primary.btn-block.btn-lg Portland - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#raleigh-durham.btn.btn-primary.btn-block.btn-lg Raleigh-Durham - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#san-francisco.btn.btn-primary.btn-block.btn-lg San Fransisco - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#seattle.btn.btn-primary.btn-block.btn-lg Seattle - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#singapore.btn.btn-primary.btn-block.btn-lg Singapore - .col-xs-12.col-sm-6.col-md-4.btn-nav - 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 - button#0.btn.btn-primary.btn-block.btn-lg(href='#') $0 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#20000.btn.btn-primary.btn-block.btn-lg(href='#') $20,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#30000.btn.btn-primary.btn-block.btn-lg(href='#') $30,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#40000.btn.btn-primary.btn-block.btn-lg(href='#') $40,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#50000.btn.btn-primary.btn-block.btn-lg(href='#') $50,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#60000.btn.btn-primary.btn-block.btn-lg(href='#') $60,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#70000.btn.btn-primary.btn-block.btn-lg(href='#') $70,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#80000.btn.btn-primary.btn-block.btn-lg(href='#') $80,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#90000.btn.btn-primary.btn-block.btn-lg(href='#') $90,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#100000.btn.btn-primary.btn-block.btn-lg(href='#') $100,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#120000.btn.btn-primary.btn-block.btn-lg(href='#') $120,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#140000.btn.btn-primary.btn-block.btn-lg(href='#') $140,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - button#160000.btn.btn-primary.btn-block.btn-lg(href='#') $160,000 - .col-xs-12.col-sm-6.col-md-4.btn-nav - 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 + .text-center + button#transform.btn.btn-primary.btn-lg Transform + .button-spacer + a(href='/coding-bootcamp-cost-calculator.json') View Data Source JSON + span   •   + a(href='/coding-bootcamp-cost-calculator') Recalculate + h3 Notes: + ol + li.large-li For cash-up-front bootcamps, we assumed an APR of 6% and a term of 3 years. + li.large-li For wage-garnishing bootcamps, we assume 18% of first year wages at their advertised starting annual salary of around $100,000. + li.large-li We assume a cost of living of $500 for cities like San Francisco and New York City, and $400 per week for everywhere else. + li.large-li The most substantial cost for most people is lost wages. A 40-hour-per-week job at the US Federal minimum wage would pay at least $15,000 per year. You can read more about economic cost + a(href='https://en.wikipedia.org/wiki/Economic_cost' target='_blank') here + | . + li.large-li Free Code Camp. We don't charge tuition or garnish wages. We're fully online so you don't have to move. We're self-paced so you don't have to quit your job. Thus, your true cost of attending Free Code Camp will be $0. From a788d2954d1805d4601155b44198172d2a8d9b7b Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 16 Jun 2015 14:53:18 -0700 Subject: [PATCH 4/4] add hacked loopback version this version avoids cheking indexOf on falsey values --- gulpfile.js | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 46c6cbd82d1..e5cd4055281 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -78,6 +78,8 @@ gulp.task('less', function() { .pipe(gulp.dest('./public/css/')); }); +gulp.task('build', ['less']); + gulp.task('watch', ['less', 'serve', 'sync'], function() { gulp.watch('./public/css/*.less', ['less']); }); diff --git a/package.json b/package.json index 97277d8279c..d156bb0873d 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "less": "~1.7.5", "less-middleware": "~2.0.1", "lodash": "^3.9.3", - "loopback": "^2.18.0", + "loopback": "https://github.com/FreeCodeCamp/loopback.git#fix/no-password", "loopback-boot": "^2.8.0", "loopback-component-passport": "git://github.com/FreeCodeCamp/loopback-component-passport.git#feature/emailOptional", "loopback-connector-mongodb": "^1.10.0",