diff --git a/client/package-lock.json b/client/package-lock.json
index 27e8eb2d20..8da82e5cf1 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -3981,6 +3981,19 @@
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII="
},
+ "d3": {
+ "version": "3.5.17",
+ "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz",
+ "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g="
+ },
+ "d3-3": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/d3-3/-/d3-3-0.0.0.tgz",
+ "integrity": "sha1-xrj+3d590sqJMiKxJ7n9qY41Aq0=",
+ "requires": {
+ "d3": "^3"
+ }
+ },
"cheerio": {
"version": "1.0.0-rc.3",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz",
@@ -4030,8 +4043,7 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"aproba": {
"version": "1.2.0",
@@ -4049,13 +4061,11 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -4068,18 +4078,15 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"core-util-is": {
"version": "1.0.2",
@@ -4182,8 +4189,7 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"ini": {
"version": "1.3.5",
@@ -4193,7 +4199,6 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -4206,20 +4211,17 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -4236,7 +4238,6 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -4309,8 +4310,7 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"object-assign": {
"version": "4.1.1",
@@ -4320,7 +4320,6 @@
"once": {
"version": "1.4.0",
"bundled": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -4396,8 +4395,7 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4427,7 +4425,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -4445,7 +4442,6 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
- "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -4484,13 +4480,11 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true,
- "optional": true
+ "bundled": true
}
}
},
@@ -14664,7 +14658,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}
}
diff --git a/client/package.json b/client/package.json
index 2d07dd5e0b..18dada10cc 100644
--- a/client/package.json
+++ b/client/package.json
@@ -20,6 +20,7 @@
"axios": "^0.19.0",
"browser-cookies": "^1.2.0",
"chai": "^4.2.0",
+ "d3-3": "0.0.0",
"date-fns": "^1.30.1",
"entities": "^1.1.2",
"enzyme": "^3.10.0",
diff --git a/client/src/templates/Guide/components/calculators/CodingBootcampCostCalculator/CodingBootcampCostCalculator.css b/client/src/templates/Guide/components/calculators/CodingBootcampCostCalculator/CodingBootcampCostCalculator.css
new file mode 100644
index 0000000000..5291016152
--- /dev/null
+++ b/client/src/templates/Guide/components/calculators/CodingBootcampCostCalculator/CodingBootcampCostCalculator.css
@@ -0,0 +1,39 @@
+.article .d3-chart {
+ position: relative;
+}
+
+.article div.tooltip {
+ position: absolute;
+ text-align: center;
+ width: 80px;
+ padding: 5px;
+ font: 12px sans-serif;
+ background: rgba(0, 0, 0, 0.8);
+ border: 0px;
+ border-radius: 8px;
+ pointer-events: none;
+ color: #fff;
+ display: table;
+}
+
+.article div.tooltip > div {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+.article div.tooltip:after {
+ box-sizing: border-box;
+ font-size: 10px;
+ width: 100%;
+ line-height: 1;
+ color: rgba(0, 0, 0, 0.8);
+ content: "\25BC";
+ position: absolute;
+ text-align: center;
+ bottom: -9px;
+ left: 0;
+}
+
+.select-city {
+ display: block;
+}
\ No newline at end of file
diff --git a/client/src/templates/Guide/components/calculators/CodingBootcampCostCalculator/CodingBootcampCostCalculator.js b/client/src/templates/Guide/components/calculators/CodingBootcampCostCalculator/CodingBootcampCostCalculator.js
new file mode 100644
index 0000000000..5dd44a58c5
--- /dev/null
+++ b/client/src/templates/Guide/components/calculators/CodingBootcampCostCalculator/CodingBootcampCostCalculator.js
@@ -0,0 +1,228 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+import { graphql } from 'gatsby';
+
+import ArticleLayout from '../../ArticleLayout';
+
+import CostCalculatorChart from './CostCalculatorChart';
+import './CodingBootcampCostCalculator.css';
+
+const propTypes = {
+ data: PropTypes.object
+};
+
+class CostCalculator extends React.Component {
+ state = {
+ cities: [],
+ incomes: [],
+ city: null,
+ cityLabel: '_______',
+ lastYearsIncome: null,
+ lastYearsIncomeLabel: '_______',
+ bootcamps: null
+ };
+
+ initComponent() {
+ const bootcamps = JSON.parse(
+ document.getElementById('bootcamps').innerHTML
+ );
+
+ document
+ .getElementById('bootcamps-data-link')
+ .setAttribute(
+ 'href',
+ `data:text/plain;charset=utf-8,${encodeURIComponent(
+ JSON.stringify(bootcamps, null, 2)
+ )}`
+ );
+
+ this.setState({
+ bootcamps: bootcamps,
+ cities: bootcamps
+ .reduce((previous, current) => {
+ return previous.concat(current.cities);
+ }, [])
+ .filter((city, idx, me) => {
+ return me.indexOf(city) === idx;
+ })
+ .sort(),
+ incomes: [
+ '0',
+ '10000',
+ '20000',
+ '30000',
+ '40000',
+ '50000',
+ '60000',
+ '70000',
+ '80000',
+ '90000',
+ '100000',
+ '120000',
+ '140000',
+ '160000',
+ '180000',
+ '200000'
+ ]
+ });
+
+ const selectCityDiv = document.getElementById('select-city');
+ const selectIncomeDiv = document.getElementById('select-income');
+ const cityLabelSpan = document.getElementById('city-label');
+ const lastYearsIncomeLabelSpan = document.getElementById(
+ 'last-years-income-label'
+ );
+ const chartComponentDiv = document.getElementById('chart-component');
+ this.setState({
+ init: true,
+ selectCityDiv,
+ selectIncomeDiv,
+ cityLabelSpan,
+ lastYearsIncomeLabelSpan,
+ chartComponentDiv
+ });
+
+ this.handleCitySelector = this.handleCitySelector.bind(this);
+ this.handleIncomeSelector = this.handleIncomeSelector.bind(this);
+ }
+
+ componentDidMount() {
+ this.initComponent();
+ }
+
+ handleCitySelector(event) {
+ let el = event.target;
+ this.setState({
+ city: event.target.value,
+ cityLabel: el.options[el.selectedIndex].text
+ });
+ }
+
+ handleIncomeSelector(event) {
+ let el = event.target;
+ this.setState({
+ lastYearsIncome: parseInt(event.target.value, 10),
+ lastYearsIncomeLabel: el.options[el.selectedIndex].text
+ });
+ }
+
+ renderSelectCity() {
+ return (
+
+ );
+ }
+
+ renderSelectIncome() {
+ return (
+
+ );
+ }
+
+ renderChartComponent() {
+ return (
+
+ Suzanne is an emergency medicine physician, triathlon + coach and web developer from Pittsburgh. You should + + + follow her on Twitter + . +
+