parent
50ea537973
commit
48b17d4dbf
@ -1,278 +0,0 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
// TODO - zoom/pan on graph
|
|
||||||
>>>>>>> 01a00149b25d7d1ed5b3d04ac16a1a1956d8dd94
|
|
||||||
const WIDTH = 800;
|
|
||||||
const HEIGHT = 600;
|
|
||||||
const parseTime = d3.timeParse("%B %e, %Y");
|
|
||||||
|
|
||||||
let instances;
|
|
||||||
let xScale, yScale;
|
|
||||||
let highlighted = []
|
|
||||||
let courses = []
|
|
||||||
let metros = []
|
|
||||||
let yAxis = 'outcomes'
|
|
||||||
let bottomAxis;
|
|
||||||
let leftAxis;
|
|
||||||
|
|
||||||
const randomColor = ()=>{
|
|
||||||
const red = Math.floor(Math.random()*128) + 64;
|
|
||||||
const green = Math.floor(Math.random()*128) + 64;
|
|
||||||
const blue = Math.floor(Math.random()*128) + 64;
|
|
||||||
return `rgb(${red}, ${green}, ${blue})`
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderTable = () => {
|
|
||||||
let trs = d3.select('tbody')
|
|
||||||
.selectAll('tr')
|
|
||||||
.data(highlighted, d => d.instance_id)
|
|
||||||
|
|
||||||
trs.exit().remove()
|
|
||||||
|
|
||||||
trs = trs
|
|
||||||
.enter()
|
|
||||||
.append('tr')
|
|
||||||
.style('background-color', datum => datum.color)
|
|
||||||
|
|
||||||
trs.selectAll('td')
|
|
||||||
.data(d => [
|
|
||||||
d.instance_id,
|
|
||||||
d.course,
|
|
||||||
d.graduation_date,
|
|
||||||
d.total_students,
|
|
||||||
`${d.dropped} (${Math.floor(d.dropped/d.total_students*100)}%)`,
|
|
||||||
d.graduates,
|
|
||||||
`${d.ninety_day_outcomes} (${Math.floor(d.ninety_day_outcomes/d.graduates*100)}%)`
|
|
||||||
], d => d.instance_id)
|
|
||||||
.enter()
|
|
||||||
.append('td')
|
|
||||||
.text(value => value)
|
|
||||||
|
|
||||||
trs.append('td')
|
|
||||||
.append('button')
|
|
||||||
.text('Deselect')
|
|
||||||
.on('click', (event, datum) =>{
|
|
||||||
event.preventDefault();
|
|
||||||
delete datum.color
|
|
||||||
highlighted = highlighted.filter(i => i.instance_id != datum.instance_id)
|
|
||||||
renderPoints()
|
|
||||||
renderTable()
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderPoints = () => {
|
|
||||||
d3.select('#points')
|
|
||||||
.selectAll('circle')
|
|
||||||
.data(instances, d => d.id)
|
|
||||||
.enter()
|
|
||||||
.append('circle');
|
|
||||||
|
|
||||||
d3.selectAll('circle')
|
|
||||||
.attr('cy', (datum, index) => {
|
|
||||||
if(yAxis === 'outcomes'){
|
|
||||||
return yScale(datum.ninety_day_outcomes/datum.graduates*100)
|
|
||||||
} else {
|
|
||||||
return yScale(datum.dropped/datum.total_students*100)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.attr('cx', (datum, index) => xScale(parseTime(datum.graduation_date)) )
|
|
||||||
.attr('fill', datum => datum.color? datum.color : 'black')
|
|
||||||
.attr('r', datum => datum.color? 10 : 5)
|
|
||||||
.style('display', (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 'block'
|
|
||||||
} else {
|
|
||||||
return 'none'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('click', (event, datum) => {
|
|
||||||
const found = highlighted.find(i => i.instance_id === datum.instance_id)
|
|
||||||
|
|
||||||
if(found === undefined){
|
|
||||||
datum.color = randomColor()
|
|
||||||
highlighted.push(datum)
|
|
||||||
} else {
|
|
||||||
delete datum.color
|
|
||||||
highlighted = highlighted.filter(i => i.instance_id != datum.instance_id)
|
|
||||||
}
|
|
||||||
renderPoints()
|
|
||||||
renderTable()
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const setupGraph = ()=>{
|
|
||||||
d3.select('svg');
|
|
||||||
d3.select('svg')
|
|
||||||
.style('width', WIDTH)
|
|
||||||
.style('height', HEIGHT);
|
|
||||||
|
|
||||||
xScale = d3.scaleTime();
|
|
||||||
xScale.range([0,WIDTH]);
|
|
||||||
const xDomain = d3.extent(instances, (datum, index) => {
|
|
||||||
return parseTime(datum.graduation_date);
|
|
||||||
});
|
|
||||||
xScale.domain(xDomain);
|
|
||||||
|
|
||||||
yScale = d3.scaleLinear();
|
|
||||||
yScale.range([HEIGHT, 0]);
|
|
||||||
const yDomain = d3.extent(instances, (datum, index) => {
|
|
||||||
if(yAxis === 'outcomes'){
|
|
||||||
return datum.ninety_day_outcomes/datum.graduates*100;
|
|
||||||
} else {
|
|
||||||
return datum.dropped/datum.total_students*100;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
yScale.domain(yDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
const createAxes = () => {
|
|
||||||
bottomAxis = d3.axisBottom(xScale);
|
|
||||||
d3.select('#container')
|
|
||||||
.append('g')
|
|
||||||
.attr('id', 'x-axis')
|
|
||||||
.call(bottomAxis)
|
|
||||||
.attr('transform', 'translate(0,'+HEIGHT+')');
|
|
||||||
|
|
||||||
leftAxis = d3.axisLeft(yScale);
|
|
||||||
d3.select('#container')
|
|
||||||
.append('g')
|
|
||||||
.attr('id', 'y-axis')
|
|
||||||
.call(leftAxis);
|
|
||||||
}
|
|
||||||
|
|
||||||
const createFormSubmissionHandler = () => {
|
|
||||||
d3.select('form').on('submit', (event)=>{
|
|
||||||
event.preventDefault();
|
|
||||||
const instanceID = parseInt(d3.select('input[type="text"]').property('value'))
|
|
||||||
const found = instances.find(i => i.instance_id === instanceID)
|
|
||||||
if(found !== undefined && highlighted.find(instance => instance.instance_id === instanceID) === undefined){
|
|
||||||
found.color = randomColor()
|
|
||||||
highlighted.push(found);
|
|
||||||
}
|
|
||||||
renderTable();
|
|
||||||
renderPoints();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const populateMetrosCoursesCheckboxes = ()=>{
|
|
||||||
|
|
||||||
d3.select('#courses ul')
|
|
||||||
.selectAll('li')
|
|
||||||
.data(courses)
|
|
||||||
.enter()
|
|
||||||
.append('li')
|
|
||||||
.text(d => d.course)
|
|
||||||
.append('input')
|
|
||||||
|
|
||||||
d3.selectAll('#courses ul li input')
|
|
||||||
.attr('type', 'checkbox')
|
|
||||||
.property('checked', d => d.checked)
|
|
||||||
.on('click', (event, datum)=>{
|
|
||||||
datum.checked = !datum.checked
|
|
||||||
renderPoints()
|
|
||||||
})
|
|
||||||
|
|
||||||
d3.select('#metros ul')
|
|
||||||
.selectAll('li')
|
|
||||||
.data(metros)
|
|
||||||
.enter()
|
|
||||||
.append('li')
|
|
||||||
.text(d => d.metro)
|
|
||||||
.append('input')
|
|
||||||
|
|
||||||
d3.selectAll('#metros ul li input')
|
|
||||||
.attr('type', 'checkbox')
|
|
||||||
.property('checked', d => d.checked)
|
|
||||||
.on('click', (event, datum)=>{
|
|
||||||
datum.checked = !datum.checked
|
|
||||||
renderPoints()
|
|
||||||
})
|
|
||||||
|
|
||||||
d3.select('#courses button:nth-child(2)')
|
|
||||||
.on('click', ()=>{
|
|
||||||
for(course of courses){
|
|
||||||
course.checked = true
|
|
||||||
}
|
|
||||||
renderPoints()
|
|
||||||
populateMetrosCoursesCheckboxes()
|
|
||||||
})
|
|
||||||
d3.select('#courses button:nth-child(3)')
|
|
||||||
.on('click', ()=>{
|
|
||||||
for(course of courses){
|
|
||||||
course.checked = false
|
|
||||||
}
|
|
||||||
renderPoints()
|
|
||||||
populateMetrosCoursesCheckboxes()
|
|
||||||
})
|
|
||||||
|
|
||||||
d3.select('#metros button:nth-child(2)')
|
|
||||||
.on('click', ()=>{
|
|
||||||
for(metro of metros){
|
|
||||||
metro.checked = true
|
|
||||||
}
|
|
||||||
renderPoints()
|
|
||||||
populateMetrosCoursesCheckboxes()
|
|
||||||
})
|
|
||||||
d3.select('#metros button:nth-child(3)')
|
|
||||||
.on('click', ()=>{
|
|
||||||
for(metro of metros){
|
|
||||||
metro.checked = false
|
|
||||||
}
|
|
||||||
renderPoints()
|
|
||||||
populateMetrosCoursesCheckboxes()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const createRadioButtonHanlders = ()=>{
|
|
||||||
d3.selectAll('input[type="radio"]')
|
|
||||||
.on('click', (event)=>{
|
|
||||||
yAxis = event.target.value
|
|
||||||
d3.select('#y-axis').remove()
|
|
||||||
d3.select('#x-axis').remove()
|
|
||||||
d3.selectAll('#points circle').remove();
|
|
||||||
setupGraph()
|
|
||||||
createAxes()
|
|
||||||
renderPoints()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = async ()=>{
|
|
||||||
instances = await d3.json('/instances');
|
|
||||||
for(let instance of instances){
|
|
||||||
const segments = instance.course.split('-')
|
|
||||||
|
|
||||||
if(metros.find(m => m.metro === segments[0]) === undefined){
|
|
||||||
metros.push({ checked:true, metro: segments[0] })
|
|
||||||
}
|
|
||||||
|
|
||||||
if(courses.find(c => c.course === segments[1]) === undefined){
|
|
||||||
courses.push({ checked:true, course: segments[1] })
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
populateMetrosCoursesCheckboxes()
|
|
||||||
setupGraph();
|
|
||||||
createAxes();
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const zoom = d3.zoom()
|
|
||||||
.on('zoom', zoomCallback);
|
|
||||||
d3.select('#container').call(zoom);
|
|
||||||
}
|
|
||||||
Loading…
Reference in new issue