Matt Huntington 2 years ago
commit ddac0ed242

@ -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()
}

@ -23,6 +23,15 @@
90 Day Outcomes %<input checked name="y-axis" type="radio" value="outcomes"/>
Dropped %<input name="y-axis" type="radio" value="dropped"/>
</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">
<h3>Choose Metros To Display</h3>
<button>All</button>

Loading…
Cancel
Save