function rocChart(id, data, options) { // set default configuration var cfg = { "margin": {top: 30, right: 20, bottom: 70, left: 61}, "width": 470, "height": 450, "interpolationMode": "basis", "ticks": undefined, "tickValues": [0, .1, .25, .5, .75, .9, 1], "fpr": "fpr", "tprVariables": [{ "name": "tpr0", }], "animate": true } //Put all of the options into a variable called cfg if('undefined' !== typeof options){ for(var i in options){ if('undefined' !== typeof options[i]){ cfg[i] = options[i]; } }//for i }//if var tprVariables = cfg["tprVariables"]; // if values for labels are not specified // set the default values for the labels to the corresponding // true positive rate variable name tprVariables.forEach(function(d, i) { if('undefined' === typeof d.label){ d.label = d.name; } }) console.log("tprVariables", tprVariables); var interpolationMode = cfg["interpolationMode"], fpr = cfg["fpr"], width = cfg["width"], height = cfg["height"], animate = cfg["animate"] var format = d3.format('.2'); var aucFormat = d3.format('.4r') var x = d3.scale.linear().range([0, width]); var y = d3.scale.linear().range([height, 0]); var color = d3.scale.category10() // d3.scale.ordinal().range(["steelblue", "red", "green", "purple"]); var xAxis = d3.svg.axis() .scale(x) .orient("top") .outerTickSize(0); var yAxis = d3.svg.axis() .scale(y) .orient("right") .outerTickSize(0); // set the axis ticks based on input parameters, // if ticks or tickValues are specified if('undefined' !== typeof cfg["ticks"]) { xAxis.ticks(cfg["ticks"]); yAxis.ticks(cfg["ticks"]); } else if ('undefined' !== typeof cfg["tickValues"]) { xAxis.tickValues(cfg["tickValues"]); yAxis.tickValues(cfg["tickValues"]); } else { xAxis.ticks(5); yAxis.ticks(5); } // apply the format to the ticks we chose xAxis.tickFormat(format); yAxis.tickFormat(format); // a function that returns a line generator function curve(data, tpr) { var lineGenerator = d3.svg.line() .interpolate(interpolationMode) .x(function(d) { return x(d[fpr]); }) .y(function(d) { return y(d[tpr]); }); return lineGenerator(data); } // a function that returns an area generator function areaUnderCurve(data, tpr) { var areaGenerator = d3.svg.area() .x(function(d) { return x(d[fpr]); }) .y0(height) .y1(function(d) { return y(d[tpr]); }); return areaGenerator(data); } var svg = d3.select("#roc") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); x.domain([0, 1]); y.domain([0, 1]); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("x", width / 2) .attr("y", 40 ) .style("text-anchor", "middle") .text("False Positive Rate") var xAxisG = svg.select("g.x.axis"); // draw the top boundary line xAxisG.append("line") .attr({ "x1": -1, "x2": width + 1, "y1": -height, "y2": -height }); // draw a bottom boundary line over the existing // x-axis domain path to make even corners xAxisG.append("line") .attr({ "x1": -1, "x2": width + 1, "y1": 0, "y2": 0 }); // position the axis tick labels below the x-axis xAxisG.selectAll('.tick text') .attr('transform', 'translate(0,' + 25 + ')'); // hide the y-axis ticks for 0 and 1 xAxisG.selectAll("g.tick line") .style("opacity", function(d) { // if d is an integer return d % 1 === 0 ? 0 : 1; }); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", -35) // manually configured so that the label is centered vertically .attr("x", 0 - height/1.56) .style("font-size","12px") .style("text-anchor", "left") .text("True Positive Rate"); yAxisG = svg.select("g.y.axis"); // add the right boundary line yAxisG.append("line") .attr({ "x1": width, "x2": width, "y1": 0, "y2": height }) // position the axis tick labels to the right of // the y-axis and // translate the first and the last tick labels // so that they are right aligned // or even with the 2nd digit of the decimal number // tick labels yAxisG.selectAll("g.tick text") .attr('transform', function(d) { if(d % 1 === 0) { // if d is an integer return 'translate(' + -22 + ',0)'; } else if((d*10) % 1 === 0) { // if d is a 1 place decimal return 'translate(' + -32 + ',0)'; } else { return 'translate(' + -42 + ',0)'; } }) // hide the y-axis ticks for 0 and 1 yAxisG.selectAll("g.tick line") .style("opacity", function(d) { // if d is an integer return d % 1 === 0 ? 0 : 1; }); // draw the random guess line svg.append("line") .attr("class", "curve") .style("stroke", "black") .attr({ "x1": 0, "x2": width, "y1": height, "y2": 0 }) .style({ "stroke-width": 2, "stroke-dasharray": "8", "opacity": 0.4 }) // draw the ROC curves function drawCurve(data, tpr, stroke){ svg.append("path") .attr("class", "curve") .style("stroke", stroke) .attr("d", curve(data, tpr)) .on('mouseover', function(d) { var areaID = "#" + tpr + "Area"; svg.select(areaID) .style("opacity", .4) var aucText = "." + tpr + "text"; svg.selectAll(aucText) .style("opacity", .9) }) .on('mouseout', function(){ var areaID = "#" + tpr + "Area"; svg.select(areaID) .style("opacity", 0) var aucText = "." + tpr + "text"; svg.selectAll(aucText) .style("opacity", 0) }); } // draw the area under the ROC curves function drawArea(data, tpr, fill) { svg.append("path") .attr("class", "area") .attr("id", tpr + "Area") .style({ "fill": fill, "opacity": 0 }) .attr("d", areaUnderCurve(data, tpr)) } function drawAUCText(auc, tpr, label) { svg.append("g") .attr("class", tpr + "text") .style("opacity", 0) .attr("transform", "translate(" + .5*width + "," + .79*height + ")") .append("text") .text(label) .style({ "fill": "white", "font-size": 18 }); svg.append("g") .attr("class", tpr + "text") .style("opacity", 0) .attr("transform", "translate(" + .5*width + "," + .84*height + ")") .append("text") .text("AUC = " + aucFormat(auc)) .style({ "fill": "white", "font-size": 18 }); } // calculate the area under each curve tprVariables.forEach(function(d){ var tpr = d.name; var points = generatePoints(data, fpr, tpr); var auc = calculateArea(points); d["auc"] = auc; }) console.log("tprVariables", tprVariables); // draw curves, areas, and text for each // true-positive rate in the data tprVariables.forEach(function(d, i){ console.log("drawing the curve for", d.label) console.log("color(", i, ")", color(i)); var tpr = d.name; drawArea(data, tpr, color(i)) drawCurve(data, tpr, color(i)); drawAUCText(d.auc, tpr, d.label); }) /////////////////////////////////////////////////// ////// animate through areas for each curve /////// /////////////////////////////////////////////////// if(animate && animate !== "false") { //sort tprVariables ascending by AUC var tprVariablesAscByAUC = tprVariables.sort(function(a, b) { return a.auc - b.auc; }) console.log("tprVariablesAscByAUC", tprVariablesAscByAUC); for(var i = 0; i < tprVariablesAscByAUC.length; i++) { areaID = "#" + tprVariablesAscByAUC[i]["name"] + "Area"; svg.select(areaID) .transition() .delay(2000 * (i+1)) .duration(250) .style("opacity", .4) .transition() .delay(2000 * (i+2)) .duration(250) .style("opacity", 0) textClass = "." + tprVariablesAscByAUC[i]["name"] + "text"; svg.selectAll(textClass) .transition() .delay(2000 * (i+1)) .duration(250) .style("opacity", .9) .transition() .delay(2000 * (i+2)) .duration(250) .style("opacity", 0) } } /////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// function generatePoints(data, x, y) { var points = []; data.forEach(function(d){ points.push([ Number(d[x]), Number(d[y]) ]) }) return points; } // numerical integration function calculateArea(points) { var area = 0.0; var length = points.length; if (length <= 2) { return area; } points.forEach(function(d, i) { var x = 0, y = 1; if('undefined' !== typeof points[i-1]){ area += (points[i][x] - points[i-1][x]) * (points[i-1][y] + points[i][y]) / 2; } }); return area; } } // rocChart