diff --git a/public/app.js b/public/app.js
index fa806ce..5fe9fd5 100644
--- a/public/app.js
+++ b/public/app.js
@@ -1,4 +1,3 @@
-// TODO - styling
const WIDTH = 800;
const HEIGHT = 600;
const parseTime = d3.timeParse("%B %e, %Y");
@@ -12,6 +11,11 @@ let yAxis = 'outcomes'
let bottomAxis;
let leftAxis;
let zoomScale = 1
+let averageOutcomesArray
+let sortedInstances;
+let displayAverage = false;
+let displayStandardDeviation = false;
+let displayInstances = true;
const randomColor = ()=>{
const red = Math.floor(Math.random()*128) + 64;
@@ -84,7 +88,7 @@ const renderPoints = () => {
const metro = metros.find(m => m.metro === instanceMetro)
const course = courses.find(c => c.course === instanceCourse)
- if(metro.checked && course.checked){
+ if(displayInstances && metro.checked && course.checked){
return 'block'
} else {
return 'none'
@@ -107,10 +111,9 @@ const renderPoints = () => {
}
const setupGraph = ()=>{
- d3.select('svg');
- d3.select('svg')
- .style('width', WIDTH)
- .style('height', HEIGHT);
+ d3.select('#container')
+ .attr('width', WIDTH)
+ .attr('height', HEIGHT);
xScale = d3.scaleTime();
xScale.range([0,WIDTH]);
@@ -176,6 +179,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
.on('click', (event, datum)=>{
datum.checked = !datum.checked
renderPoints()
+ displayMeanStandardDeviation()
})
d3.select('#metros ul')
@@ -192,6 +196,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
.on('click', (event, datum)=>{
datum.checked = !datum.checked
renderPoints()
+ displayMeanStandardDeviation()
})
d3.select('#courses button:nth-child(2)')
@@ -200,6 +205,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
course.checked = true
}
renderPoints()
+ displayMeanStandardDeviation()
populateMetrosCoursesCheckboxes()
})
d3.select('#courses button:nth-child(3)')
@@ -208,6 +214,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
course.checked = false
}
renderPoints()
+ displayMeanStandardDeviation()
populateMetrosCoursesCheckboxes()
})
@@ -217,6 +224,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
metro.checked = true
}
renderPoints()
+ displayMeanStandardDeviation()
populateMetrosCoursesCheckboxes()
})
d3.select('#metros button:nth-child(3)')
@@ -225,6 +233,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
metro.checked = false
}
renderPoints()
+ displayMeanStandardDeviation()
populateMetrosCoursesCheckboxes()
})
}
@@ -239,6 +248,135 @@ const createRadioButtonHanlders = ()=>{
setupGraph()
createAxes()
renderPoints()
+ displayMeanStandardDeviation();
+ })
+}
+
+const setUpZoomPan = ()=>{
+ const zoomCallback = (event) => {
+ d3.select('#points').attr("transform", event.transform);
+ d3.select('#x-axis')
+ .call(bottomAxis.scale(event.transform.rescaleX(xScale)));
+ d3.select('#y-axis')
+ .call(leftAxis.scale(event.transform.rescaleY(yScale)));
+
+ if(event.transform.k !== zoomScale){
+ zoomScale = event.transform.k
+ d3.selectAll('circle')
+ .attr('r', datum => (datum.color ? 10 : 5) / zoomScale);
+ }
+ }
+
+ const zoom = d3.zoom()
+ .on('zoom', zoomCallback);
+ d3.select('#container').call(zoom);
+}
+
+const getAverage = (start, end) => {
+ let sumOutcomes = 0
+ let sumDropped = 0
+ for(let i = start; i < end; i++){
+ sumOutcomes += sortedInstances[i].ninety_day_outcomes/sortedInstances[i].graduates*100
+ sumDropped += sortedInstances[i].dropped/sortedInstances[i].total_students*100
+ }
+ const averageOutcomes = sumOutcomes/(end-start)
+ const averageDropped = sumDropped/(end-start)
+
+ let sumOutcomesDifferences = 0
+ let sumDroppedDifferences = 0
+ for(let i = start; i < end; i++){
+ sumOutcomesDifferences += Math.pow(sortedInstances[i].ninety_day_outcomes/sortedInstances[i].graduates*100 - averageOutcomes,2)
+ sumDroppedDifferences += Math.pow(sortedInstances[i].dropped/sortedInstances[i].total_students*100 - averageDropped,2)
+ }
+ const stdDevOutcomes = Math.sqrt(sumOutcomesDifferences/(end-start))
+ const stdDevDropped = Math.sqrt(sumDroppedDifferences/(end-start))
+
+ averageOutcomesArray.push({
+ averageOutcomes,
+ averageDropped,
+ stdDevOutcomes,
+ stdDevDropped,
+ initialGraduationDate:sortedInstances[start].graduation_date
+ })
+}
+
+const displayMeanStandardDeviation = () => {
+ sortedInstances = instances.sort((a,b) => Date.parse(a.graduation_date) - Date.parse(b.graduation_date))
+ sortedInstances = sortedInstances.filter(datum=> {
+ const instanceMetro = datum.course.split('-')[0]
+ const instanceCourse = datum.course.split('-')[1]
+ const metro = metros.find(m => m.metro === instanceMetro)
+ const course = courses.find(c => c.course === instanceCourse)
+ if(metro.checked && course.checked){
+ return true
+ } else {
+ return false
+ }
+ })
+
+ averageOutcomesArray = []
+ for(let i = 0; i < sortedInstances.length-10; i += 10){
+ getAverage(i,i+10)
+ }
+
+ d3.selectAll('#points path').remove();
+
+ if(displayAverage){
+
+ d3.select('#points')
+ .append('path')
+ .datum(averageOutcomesArray)
+ .attr('fill', 'none')
+ .attr('stroke', 'steelblue')
+ .attr('stroke-width', 2)
+ .attr('opacity', 0.7)
+ .attr('d', d3.line()
+ .x(d => xScale(parseTime(d.initialGraduationDate)))
+ .y(d => (yAxis === 'outcomes') ? yScale(d.averageOutcomes) : yScale(d.averageDropped))
+ )
+ }
+ if(displayStandardDeviation){
+ d3.select('#points')
+ .append('path')
+ .datum(averageOutcomesArray)
+ .attr('fill', 'none')
+ .attr('stroke', 'red')
+ .attr('stroke-width', 2)
+ .attr('opacity', 0.7)
+ .attr('d', d3.line()
+ .x(d => xScale(parseTime(d.initialGraduationDate)))
+ .y(d => (yAxis === 'outcomes') ? yScale(d.averageOutcomes+d.stdDevOutcomes) : yScale(d.averageDropped+d.stdDevDropped))
+ )
+
+ d3.select('#points')
+ .append('path')
+ .datum(averageOutcomesArray)
+ .attr('fill', 'none')
+ .attr('stroke', 'red')
+ .attr('stroke-width', 2)
+ .attr('opacity', 0.7)
+ .attr('d', d3.line()
+ .x(d => xScale(parseTime(d.initialGraduationDate)))
+ .y(d => (yAxis === 'outcomes') ? yScale(d.averageOutcomes-d.stdDevOutcomes) : yScale(d.averageDropped-d.stdDevDropped))
+ )
+ }
+}
+
+const setUpDisplayAverageHandler = ()=>{
+ d3.select('#average input:nth-child(2)')
+ .on('click', (event)=>{
+ displayAverage = event.target.checked
+ displayMeanStandardDeviation()
+ })
+ d3.select('#average input:nth-child(3)')
+ .on('click', (event)=>{
+ displayStandardDeviation = event.target.checked
+ displayMeanStandardDeviation()
+ })
+ d3.select('#average input:nth-child(4)')
+ .on('click', (event)=>{
+ displayInstances = event.target.checked
+ renderPoints()
})
}
@@ -262,21 +400,8 @@ window.onload = async ()=>{
renderPoints();
createFormSubmissionHandler();
createRadioButtonHanlders();
- const zoomCallback = (event) => {
- d3.select('#points').attr("transform", event.transform);
- d3.select('#x-axis')
- .call(bottomAxis.scale(event.transform.rescaleX(xScale)));
- d3.select('#y-axis')
- .call(leftAxis.scale(event.transform.rescaleY(yScale)));
+ setUpZoomPan();
- if(event.transform.k !== zoomScale){
- zoomScale = event.transform.k
- d3.selectAll('circle')
- .attr('r', datum => (datum.color ? 10 : 5) / zoomScale);
- }
- }
-
- const zoom = d3.zoom()
- .on('zoom', zoomCallback);
- d3.select('#container').call(zoom);
+ displayMeanStandardDeviation();
+ setUpDisplayAverageHandler()
}
diff --git a/public/index.html b/public/index.html
index 81be8c0..53427e0 100644
--- a/public/index.html
+++ b/public/index.html
@@ -23,6 +23,15 @@
90 Day Outcomes %
Dropped %
+
Choose Metros To Display