Matt Huntington 2 years ago
commit ddac0ed242

@ -1,4 +1,3 @@
// TODO - styling
const WIDTH = 800; const WIDTH = 800;
const HEIGHT = 600; const HEIGHT = 600;
const parseTime = d3.timeParse("%B %e, %Y"); const parseTime = d3.timeParse("%B %e, %Y");
@ -12,6 +11,11 @@ let yAxis = 'outcomes'
let bottomAxis; let bottomAxis;
let leftAxis; let leftAxis;
let zoomScale = 1 let zoomScale = 1
let averageOutcomesArray
let sortedInstances;
let displayAverage = false;
let displayStandardDeviation = false;
let displayInstances = true;
const randomColor = ()=>{ const randomColor = ()=>{
const red = Math.floor(Math.random()*128) + 64; const red = Math.floor(Math.random()*128) + 64;
@ -84,7 +88,7 @@ const renderPoints = () => {
const metro = metros.find(m => m.metro === instanceMetro) const metro = metros.find(m => m.metro === instanceMetro)
const course = courses.find(c => c.course === instanceCourse) const course = courses.find(c => c.course === instanceCourse)
if(metro.checked && course.checked){ if(displayInstances && metro.checked && course.checked){
return 'block' return 'block'
} else { } else {
return 'none' return 'none'
@ -107,10 +111,9 @@ const renderPoints = () => {
} }
const setupGraph = ()=>{ const setupGraph = ()=>{
d3.select('svg'); d3.select('#container')
d3.select('svg') .attr('width', WIDTH)
.style('width', WIDTH) .attr('height', HEIGHT);
.style('height', HEIGHT);
xScale = d3.scaleTime(); xScale = d3.scaleTime();
xScale.range([0,WIDTH]); xScale.range([0,WIDTH]);
@ -176,6 +179,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
.on('click', (event, datum)=>{ .on('click', (event, datum)=>{
datum.checked = !datum.checked datum.checked = !datum.checked
renderPoints() renderPoints()
displayMeanStandardDeviation()
}) })
d3.select('#metros ul') d3.select('#metros ul')
@ -192,6 +196,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
.on('click', (event, datum)=>{ .on('click', (event, datum)=>{
datum.checked = !datum.checked datum.checked = !datum.checked
renderPoints() renderPoints()
displayMeanStandardDeviation()
}) })
d3.select('#courses button:nth-child(2)') d3.select('#courses button:nth-child(2)')
@ -200,6 +205,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
course.checked = true course.checked = true
} }
renderPoints() renderPoints()
displayMeanStandardDeviation()
populateMetrosCoursesCheckboxes() populateMetrosCoursesCheckboxes()
}) })
d3.select('#courses button:nth-child(3)') d3.select('#courses button:nth-child(3)')
@ -208,6 +214,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
course.checked = false course.checked = false
} }
renderPoints() renderPoints()
displayMeanStandardDeviation()
populateMetrosCoursesCheckboxes() populateMetrosCoursesCheckboxes()
}) })
@ -217,6 +224,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
metro.checked = true metro.checked = true
} }
renderPoints() renderPoints()
displayMeanStandardDeviation()
populateMetrosCoursesCheckboxes() populateMetrosCoursesCheckboxes()
}) })
d3.select('#metros button:nth-child(3)') d3.select('#metros button:nth-child(3)')
@ -225,6 +233,7 @@ const populateMetrosCoursesCheckboxes = ()=>{
metro.checked = false metro.checked = false
} }
renderPoints() renderPoints()
displayMeanStandardDeviation()
populateMetrosCoursesCheckboxes() populateMetrosCoursesCheckboxes()
}) })
} }
@ -239,6 +248,135 @@ const createRadioButtonHanlders = ()=>{
setupGraph() setupGraph()
createAxes() createAxes()
renderPoints() 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(); renderPoints();
createFormSubmissionHandler(); createFormSubmissionHandler();
createRadioButtonHanlders(); createRadioButtonHanlders();
const zoomCallback = (event) => { setUpZoomPan();
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() displayMeanStandardDeviation();
.on('zoom', zoomCallback); setUpDisplayAverageHandler()
d3.select('#container').call(zoom);
} }

@ -23,6 +23,15 @@
90 Day Outcomes %<input checked name="y-axis" type="radio" value="outcomes"/> 90 Day Outcomes %<input checked name="y-axis" type="radio" value="outcomes"/>
Dropped %<input name="y-axis" type="radio" value="dropped"/> Dropped %<input name="y-axis" type="radio" value="dropped"/>
</section> </section>
<section id="average">
<h3>Display Average?</h3>
<input type='checkbox' />
Display Average?
<input type='checkbox' />
Display Standard Deviation?
<input type='checkbox' checked />
Display Instances?
</section>
<section id="metros"> <section id="metros">
<h3>Choose Metros To Display</h3> <h3>Choose Metros To Display</h3>
<button>All</button> <button>All</button>

Loading…
Cancel
Save