/* Developed by Luciana Cancado as an internship project at the Center for Assessment (http://www.nciea.org/) during Summer 2015. This script: - creates a bar chart using the classroom.tsv dataset - changes the color of the bars depending on a given value of a dataset variable - adds a line element to indicate a predefined cut point on the y axis when a radio button is selected - sorts the chart - adds a tooltip using D3-tip (source: https://github.com/caged/d3-tip, example: http://bl.ocks.org/Caged/6476579) For a detailed example on how to create a bar chart and explanation of the various elements of this script, check: http://bost.ocks.org/mike/bar/ */ var marginCombo = {top: 10, right: 10, bottom: 10, left: 40}, widthCombo = 860 - marginCombo.left - marginCombo.right, heightCombo = 280 - marginCombo.top - marginCombo.bottom; var colors10 = d3.scale.category10().domain(d3.range(0,10)); var colors20 = d3.scale.category20().domain(d3.range(0,20)); var decision; var scoreType; var normGroup; var critLnYCombo; var critLnXCombo; var xCombo = d3.scale.ordinal() .rangeRoundBands([0, widthCombo], 0.1); var yCombo = d3.scale.linear() .range([heightCombo, 0]); var xAxisCombo = d3.svg.axis() .scale(xCombo) .orient("bottom"); var yAxisCombo = d3.svg.axis() .scale(yCombo) .orient("left"); var tipCombo = d3.tip() .attr('class', 'd3-tip') .parent(document.getElementById('comboGraph')) //must use the attached d3.tip.v0.6.3.js for this to work .offset([-10, 0]) .html(function(d) { return "" + d.variable + ""; }); var svgCombo = d3.select(".comboGraph").append("svg") .attr("preserveAspectRatio", "none") .attr("viewBox", "0 0 " + 920 + " " + 320) .attr("width", widthCombo + marginCombo.left + marginCombo.right) .attr("height", heightCombo + marginCombo.top + marginCombo.bottom) .append("g") .attr("transform", "translate(" + marginCombo.left + "," + marginCombo.top + ")"); svgCombo.call(tipCombo); svgCombo.append("g") .attr("class", "x axis combo") .attr("transform", "translate(0," + heightCombo + ")") .append("text") .attr("class", "xaxiscombo axislabel") .attr("y", 35) .attr("x", widthCombo/2) .style("text-anchor", "middle") .text("Student Name"); svgCombo.append("g") .attr("class", "y axis combo") .append("text") .attr("class", "yaxiscombo axislabel") .attr("transform", "rotate(-90)") .attr("y", 0 - marginCombo.left) .attr("x", 0 - (heightCombo / 2)) .attr("dy", "0.71em") .style("text-anchor", "middle") .text("Number Correct"); var tsvCombo; var dataFileCombo="classroom.tsv"; d3.tsv(dataFileCombo, function(error, data){ if (error) throw error; tsvCombo = data; tsvCombo.sort(function (a,b) {return d3.ascending(a.name, b.name);}); data.forEach(function(d){ d.variable = +d.score; d.name= d.name; }); xCombo.domain(data.map(function(d) { return d.name; })); yCombo.domain ([0,50]); var barCrit = svgCombo.selectAll(".barCombo") .data(data); barCrit.enter().append("rect"); barCrit.exit().remove(); barCrit.attr("class", "barCombo") .attr("x", function(d) { return xCombo(d.name); }) .attr("width", xCombo.rangeBand()) .attr("y", function(d) { return yCombo(d.variable); }) .attr("height", function(d) { return heightCombo - yCombo(d.variable); }) .on('mouseover', tipCombo.show) .on('mouseout', tipCombo.hide) .attr("fill",function(d,i){ if (d.name == 'Mary') { return 'purple'; } else { return colors10(7);} ;//grey }) ; d3.select('.x.axis.combo') .call(xAxisCombo) .selectAll("text") .style("text-anchor", "middle") ; d3.select('.y.axis.combo') .call(yAxisCombo) ; }); // Returns the value of the selected myradio function getRadioValue(myRadio) { for(var i = 0; i < myRadio.length; i++){ if(myRadio[i].checked){ return myRadio[i].value; } } }; // Updates the svg depending on the decisionType from the comboForm form function updateComboChart(decisionType) { var transitionCombo = svgCombo.transition().duration(750); var delay = function(d, i) { return i * 50; }; if (decisionType == 'D1') { var yAxisLabel = "Percentile Rank"; var xAxisLabel = "Student Name"; cutScoreComboY = 80; dataFileCombo="classroom.tsv"; d3.tsv(dataFileCombo, function(error, data){ if (error) throw error; tsvCombo = data; data.forEach(function(d){ d.variable = +d.pctRank; d.name= d.name; }); var sortedNames = tsvCombo.sort(function (a,b) {return a.variable - b.variable; }) .map(function(d) { return d.name; }); xCombo.domain(sortedNames); yCombo.domain ([0,99]); var barCrit = svgCombo.selectAll(".barCombo") .data(data); barCrit.enter().append("rect"); barCrit.exit().remove(); barCrit.attr("class", "barCombo") .transition().duration(1000) .attr("x", function(d) { return xCombo(d.name); }) .attr("width", xCombo.rangeBand()) .attr("y", function(d) { return yCombo(d.variable); }) .attr("height", function(d) { return heightCombo - yCombo(d.variable); }) .attr("fill",function(d,i){ if (d.name == 'Mary') { return 'purple'; } else { if (+d.variable < cutScoreComboY) { return colors10(3); } //red else {return colors10(0);} ;//blue } }) ; clearXCritLine(); tipCombo.html(function(d) { return "" + d.variable + ""; }); svgCombo.call(tipCombo); yAxisCombo.tickValues([1,10, 20, 30, 40, 50, 60, 70, 80, 90, 99]); xAxisCombo.tickValues(sortedNames); transitionCombo.select(".y.axis.combo") // change the y axis .call(yAxisCombo); transitionCombo.select(".yaxiscombo.axislabel") .text(yAxisLabel); transitionCombo.select(".x.axis.combo") .call(xAxisCombo); transitionCombo.select(".xaxiscombo.axislabel") .text(xAxisLabel); updateYCritLine(cutScoreComboY); }); // end of d3.tsv } // end of D1 if else if (decisionType == 'D2') { var yAxisLabel = "Number Correct"; var xAxisLabel = "Student Name"; cutScoreComboY = 45; dataFileCombo="classroom.tsv"; d3.tsv(dataFileCombo, function(error, data){ if (error) throw error; tsvCombo = data; data.forEach(function(d){ d.variable = +d.score; d.name= d.name; }); var sortedNames = tsvCombo.sort(function (a,b) {return a.variable - b.variable; }) .map(function(d) { return d.name; }); xCombo.domain(sortedNames); yCombo.domain ([0,50]); var barCrit = svgCombo.selectAll(".barCombo") .data(data); barCrit.enter().append("rect"); barCrit.exit().remove(); barCrit.attr("class", "barCombo") .transition().duration(1000) .attr("x", function(d) { return xCombo(d.name); }) .attr("width", xCombo.rangeBand()) .attr("y", function(d) { return yCombo(d.variable); }) .attr("height", function(d) { return heightCombo - yCombo(d.variable); }) //.on('mouseover', tipCombo.show) //.on('mouseout', tipCombo.hide) .attr("fill",function(d,i){ if (d.name == 'Mary') { return 'purple'; } else { if (+d.variable < cutScoreComboY) { return colors10(3); } //red else {return colors10(0);} ;//blue } }) ; clearXCritLine(); tipCombo.html(function(d) { return "" + d.variable + ""; }); svgCombo.call(tipCombo); yAxisCombo.tickValues([0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]); xAxisCombo.tickValues(sortedNames); transitionCombo.select(".y.axis.combo") // change the y axis .call(yAxisCombo); transitionCombo.select(".yaxiscombo.axislabel") .text(yAxisLabel); transitionCombo.select(".x.axis.combo") .call(xAxisCombo); transitionCombo.select(".xaxiscombo.axislabel") .text(xAxisLabel); updateYCritLine(cutScoreComboY); }); // end of d3.tsv } // end of D2 if else if (decisionType == 'D3') { var xAxisLabel = "Student Rank"; var yAxisLabel = "Number Correct"; cutScoreComboX = 20; cutScoreComboY = 35; dataFileCombo="school.tsv"; d3.tsv(dataFileCombo, function(error, data){ if (error) throw error; tsvCombo = data; data.forEach(function(d){ d.variable = +d.score; d.rank = +d.rank; d.rankName = d.rank; d.name= d.name; }); var sortedNames = tsvCombo.sort(function (a,b) {return a.variable - b.variable; }) .map(function(d) { return d.name; }); var rankNames = tsvCombo.map(function(d) { return d.rankName; }); xCombo.domain(rankNames); yCombo.domain ([0,50]); var barCrit = svgCombo.selectAll(".barCombo") .data(data); barCrit.enter().append("rect"); barCrit.exit().remove(); barCrit.attr("class", "barCombo") .transition().duration(1000) .attr("x", function(d) { return xCombo(d.rankName); }) .attr("width", xCombo.rangeBand()) .attr("y", function(d) { return yCombo(d.variable); }) .attr("height", function(d) { return heightCombo - yCombo(d.variable); }) //.on('mouseover', tipCombo.show) //.on('mouseout', tipCombo.hide) .attr("fill",function(d,i){ if (d.name == 'Mary') { return 'purple'; } else { if ( (+d.variable < cutScoreComboY) || (+d.rank > cutScoreComboX) ) { return colors10(3); } //red else {return colors10(0);} ;//blue } }) ; tipCombo.html(function(d) { return "" + d.name + ""; }); svgCombo.call(tipCombo); /* Show and hide tip on mouse events. Need to call it again since we changed the html attribute */ barCrit .on('mouseover', tipCombo.show) .on('mouseout', tipCombo.hide); yAxisCombo.tickValues([0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]); xAxisCombo.tickValues(["1","10", "20", "30", "40", "50", "60", "70", "80", "90", "100"]); transitionCombo.select(".y.axis.combo") // change the y axis .call(yAxisCombo); transitionCombo.select(".yaxiscombo.axislabel") .text(yAxisLabel); transitionCombo.select(".x.axis.combo") .call(xAxisCombo); transitionCombo.select(".xaxiscombo.axislabel") .text(xAxisLabel); updateYCritLine(cutScoreComboY); updateXCritLine(cutScoreComboX); }); // end of d3.tsv } // end of D3 if }; // end of updateComboChart function // executed by the onclick event of the combined chart submit button // gets the values of the selected radio buttons and calls the checkDecision function in file combo_quiz.js function checkCombo (myform) { decision=getRadioValue(myform.elements['decision']); scoreType=getRadioValue(myform.elements['scoreType']); normGroup=getRadioValue(myform.elements['normGroup']); /* jQuery to scroll to a given element id*/ $('html, body').animate({ scrollTop: $("#Activity4").offset().top}, 500); checkDecision (decision, scoreType, normGroup ); }; // updates the horizontal criterion line element according to cutScoreInput function updateYCritLine(cutScoreInput) { var cutScoreY = +cutScoreInput; var transitionCombo = svgCombo.transition().duration(750); var delay = function(d, i) { return i * 50; }; if (typeof critLnYCombo == "undefined") { critLnYCombo = svgCombo.append("line") .attr("class", "critLnYCombo") .attr("x1", 0) .attr("x2", widthCombo) .attr("y1", function(d) { return yCombo(cutScoreY); }) .attr("y2", function(d) { return yCombo(cutScoreY); }) .style("stroke", "rgb(0, 0, 0)") .style("stroke-widthCombo","1") .style("shape-rendering","crispEdges") .style("stroke-dasharray","10,10") ; svgCombo.append("g") .append("text") .attr("class", "critLnYComboText") .attr("y", yCombo(cutScoreY) - 10) .attr("x", 5) .attr("dy", ".35em") .style("text-anchor", "start") .style("font", "12px sans-serif") .text("Criterion") ; } transitionCombo.selectAll(".critLnYCombo") .delay(delay) .attr("y1", function(d) { return yCombo(cutScoreY); }) .attr("y2", function(d) { return yCombo(cutScoreY); }) ; transitionCombo.selectAll(".critLnYComboText") .delay(delay) .attr("y", yCombo(cutScoreY) - 10) ; }; // makes the vertical criterion line transparent function clearXCritLine() { var transitionCombo = svgCombo.transition().duration(750); transitionCombo.selectAll(".critLnXCombo") .style("stroke-opacity","0.0"); transitionCombo.selectAll(".critLnXComboText") .text("") ; }; // updates the vertical criterion line element according to cutScoreInput function updateXCritLine(cutScoreInput) { var cutScoreX = +cutScoreInput; var transitionCombo = svgCombo.transition().duration(750); var delay = function(d, i) { return i * 50; }; if (typeof critLnXCombo == "undefined") { critLnXCombo = svgCombo.append("line") .attr("class", "critLnXCombo") .attr("x1", function(d) { return xCombo(cutScoreX); }) .attr("x2", function(d) { return xCombo(cutScoreX); }) .attr("y1", 0) .attr("y2", heightCombo) .style("stroke", "rgb(0, 0, 0)") .style("stroke-widthCombo","1") .style("shape-rendering","crispEdges") .style("stroke-dasharray","10,10") ; svgCombo.append("g") .append("text") .attr("class", "critLnXComboText") .attr("y", -5) .attr("x", xCombo(cutScoreX) -10 ) .attr("dy", ".35em") .style("text-anchor", "start") .style("font", "12px sans-serif") ; } transitionCombo.selectAll(".critLnXCombo") .delay(delay) .style("stroke-opacity","1.0") .attr("x1", function(d) { return xCombo(cutScoreX); }) .attr("x2", function(d) { return xCombo(cutScoreX); }); transitionCombo.selectAll(".critLnXComboText") .delay(delay) .attr("x", xCombo(cutScoreX) -10 ) .text("Top " +cutScoreX) ; }; // uncheck the radio buttons for scoreType and normGroup function clearComboRadios(myform) { var allRadios = myform.elements['scoreType']; var i = 0; for (i = 0; i < allRadios.length; i++) { allRadios[i].checked = false; } allRadios = myform.elements['normGroup']; for (i = 0; i < allRadios.length; i++) { allRadios[i].checked = false; } };