package water.api;
import hex.ConfusionMatrix;
import org.apache.commons.lang.StringEscapeUtils;
import static water.api.AUC.ThresholdCriterion;
import static water.api.AUC.isBetter;
import water.api.Request.*;
import water.*;
import java.util.HashSet;
public class AUCData extends Iced {
static final int API_WEAVER = 1; // This file has auto-gen'd doc & json fields
static public DocGen.FieldDoc[] DOC_FIELDS; // Initialized from Auto-Gen code.
@API(help = "Thresholds (optional, e.g. 0:1:0.01 or 0.0,0.2,0.4,0.6,0.8,1.0).", json = true)
public float[] thresholds;
@API(help = "Threshold criterion", json = true)
public ThresholdCriterion threshold_criterion = ThresholdCriterion.maximum_F1;
@API(help="domain of the actual response", json=true)
private String [] actual_domain;
@API(help="AUC (ROC)", json=true)
public double AUC;
@API(help="Gini", json=true)
public double Gini;
@API(help = "Confusion Matrices for all thresholds", json=true)
public long[][][] confusion_matrices;
@API(help = "F1 for all thresholds", json=true)
public float[] F1;
@API(help = "F2 for all thresholds", json=true)
public float[] F2;
@API(help = "F0point5 for all thresholds", json=true)
public float[] F0point5;
@API(help = "Accuracy for all thresholds", json=true)
public float[] accuracy;
@API(help = "Error for all thresholds", json=true)
public float[] errorr;
@API(help = "Precision for all thresholds", json=true)
public float[] precision;
@API(help = "Recall for all thresholds", json=true)
public float[] recall;
@API(help = "Specificity for all thresholds", json=true)
public float[] specificity;
@API(help = "MCC for all thresholds", json=true)
public float[] mcc;
@API(help = "Max per class error for all thresholds", json=true)
public float[] max_per_class_error;
@API(help="Threshold criteria", json=true)
String[] threshold_criteria;
@API(help="Optimal thresholds for criteria", json=true)
private float[] threshold_for_criteria;
@API(help="F1 for threshold criteria", json=true)
private float[] F1_for_criteria;
@API(help="F2 for threshold criteria", json=true)
private float[] F2_for_criteria;
@API(help="F0point5 for threshold criteria", json=true)
private float[] F0point5_for_criteria;
@API(help="Accuracy for threshold criteria", json=true)
private float[] accuracy_for_criteria;
@API(help="Error for threshold criteria", json=true)
private float[] error_for_criteria;
@API(help="Precision for threshold criteria", json=true)
private float[] precision_for_criteria;
@API(help="Recall for threshold criteria", json=true)
private float[] recall_for_criteria;
@API(help="Specificity for threshold criteria", json=true)
private float[] specificity_for_criteria;
@API(help="MCC for threshold criteria", json=true)
private float[] mcc_for_criteria;
@API(help="Maximum per class Error for threshold criteria", json=true)
private float[] max_per_class_error_for_criteria;
@API(help="Confusion Matrices for threshold criteria", json=true)
private long[][][] confusion_matrix_for_criteria;
/* Independent on thresholds */
public double AUC() { return AUC; }
public double Gini() { return Gini; }
/* Return the metrics for given criterion */
public double F1(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].F1(); }
public double F2(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].F2(); }
public double F0point5(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].F0point5(); }
public double precision(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].precision(); }
public double recall(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].recall(); }
public double specificity(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].specificity(); }
public double mcc(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].mcc(); }
public double accuracy(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].accuracy(); }
public double err(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].err(); }
public double max_per_class_error(ThresholdCriterion criter) { return _cms[idxCriter[criter.ordinal()]].max_per_class_error(); }
public float threshold(ThresholdCriterion criter) { return threshold_for_criteria[criter.ordinal()]; }
public long[][] cm(ThresholdCriterion criter) { return confusion_matrix_for_criteria[criter.ordinal()]; }
/* Return the metrics for chosen threshold criterion */
public double F1() { return F1(threshold_criterion); }
public double F2() { return F2(threshold_criterion); }
public double F0point5() { return F0point5(threshold_criterion); }
public double err() { return err(threshold_criterion); }
public double precision() { return precision(threshold_criterion); }
public double recall() { return recall(threshold_criterion); }
public double specificity() { return specificity(threshold_criterion); }
public double mcc() { return mcc(threshold_criterion); }
public double accuracy() { return accuracy(threshold_criterion); }
public double max_per_class_error() { return max_per_class_error(threshold_criterion); }
public float threshold() { return threshold(threshold_criterion); }
public long[][] cm() { return cm(threshold_criterion); }
public ConfusionMatrix CM() { return _cms[idxCriter[threshold_criterion.ordinal()]]; }
/* Return the best possible metrics */
public double bestF1() { return F1(ThresholdCriterion.maximum_F1); }
public double bestErr() { return err(ThresholdCriterion.maximum_Accuracy); }
/* Helpers */
private int[] idxCriter;
private double[] _tprs;
private double[] _fprs;
private hex.ConfusionMatrix[] _cms;
private static double trapezoid_area(double x1, double x2, double y1, double y2) { return Math.abs(x1-x2)*(y1+y2)/2.; }
public AUCData compute(hex.ConfusionMatrix[] cms, float[] thresh, String[] domain, ThresholdCriterion criter) {
_cms = cms;
thresholds = thresh;
threshold_criterion = criter;
actual_domain = domain;
assert(_cms.length == thresholds.length):("incompatible lengths of thresholds and confusion matrices: " + _cms.length + " != " + thresholds.length);
// compute AUC and best thresholds
computeAUC();
findBestThresholds(thresh);
computeMetrics();
return this;
}
private void computeAUC() {
_tprs = new double[_cms.length];
_fprs = new double[_cms.length];
double TPR_pre = 1;
double FPR_pre = 1;
AUC = 0;
for( int t = 0; t < _cms.length; ++t ) {
double TPR = 1 - _cms[t].classErr(1); // =TP/(TP+FN) = true-positive-rate
double FPR = _cms[t].classErr(0); // =FP/(FP+TN) = false-positive-rate
AUC += trapezoid_area(FPR_pre, FPR, TPR_pre, TPR);
TPR_pre = TPR;
FPR_pre = FPR;
_tprs[t] = TPR;
_fprs[t] = FPR;
}
AUC += trapezoid_area(FPR_pre, 0, TPR_pre, 0);
assert(AUC > -1e-5 && AUC < 1.+1e-5); //check numerical sanity
AUC = Math.max(0., Math.min(AUC, 1.)); //clamp to 0...1
Gini = 2*AUC-1;
}
private void findBestThresholds(float[] thresholds) {
threshold_criteria = new String[ThresholdCriterion.values().length];
int i=0;
HashSet<ThresholdCriterion> hs = new HashSet<ThresholdCriterion>();
for (ThresholdCriterion criter : ThresholdCriterion.values()) {
hs.add(criter);
threshold_criteria[i++] = criter.toString().replace("_", " ");
}
confusion_matrix_for_criteria = new long[hs.size()][][];
idxCriter = new int[hs.size()];
threshold_for_criteria = new float[hs.size()];
F1_for_criteria = new float[hs.size()];
F2_for_criteria = new float[hs.size()];
F0point5_for_criteria = new float[hs.size()];
accuracy_for_criteria = new float[hs.size()];
error_for_criteria = new float[hs.size()];
precision_for_criteria = new float[hs.size()];
recall_for_criteria = new float[hs.size()];
specificity_for_criteria = new float[hs.size()];
mcc_for_criteria = new float[hs.size()];
max_per_class_error_for_criteria = new float[hs.size()];
for (ThresholdCriterion criter : hs) {
final int id = criter.ordinal();
idxCriter[id] = 0;
threshold_for_criteria[id] = thresholds[0];
for(i = 1; i < _cms.length; ++i) {
if (isBetter(_cms[i], _cms[idxCriter[id]], criter)) {
idxCriter[id] = i;
threshold_for_criteria[id] = thresholds[i];
}
}
// Set members for JSON, float to save space
confusion_matrix_for_criteria[id] = _cms[idxCriter[id]]._arr;
F1_for_criteria[id] = (float)_cms[idxCriter[id]].F1();
F2_for_criteria[id] = (float)_cms[idxCriter[id]].F2();
F0point5_for_criteria[id] = (float)_cms[idxCriter[id]].F0point5();
accuracy_for_criteria[id] = (float)_cms[idxCriter[id]].accuracy();
error_for_criteria[id] = (float)_cms[idxCriter[id]].err();
precision_for_criteria[id] = (float)_cms[idxCriter[id]].precision();
recall_for_criteria[id] = (float)_cms[idxCriter[id]].recall();
specificity_for_criteria[id] = (float)_cms[idxCriter[id]].specificity();
mcc_for_criteria[id] = (float)_cms[idxCriter[id]].mcc();
max_per_class_error_for_criteria[id] = (float)_cms[idxCriter[id]].max_per_class_error();
}
}
/**
* Populate requested JSON fields
*/
private void computeMetrics() {
confusion_matrices = new long[_cms.length][][];
F1 = new float[_cms.length];
F2 = new float[_cms.length];
F0point5 = new float[_cms.length];
accuracy = new float[_cms.length];
errorr = new float[_cms.length];
precision = new float[_cms.length];
recall = new float[_cms.length];
specificity = new float[_cms.length];
mcc = new float[_cms.length];
max_per_class_error = new float[_cms.length];
for(int i=0;i<_cms.length;++i) {
confusion_matrices[i] = _cms[i]._arr;
F1[i] = (float)_cms[i].F1();
F2[i] = (float)_cms[i].F2();
F0point5[i] = (float)_cms[i].F0point5();
accuracy[i] = (float)_cms[i].accuracy();
errorr[i] = (float)_cms[i].err();
precision[i] = (float)_cms[i].precision();
recall[i] = (float)_cms[i].recall();
specificity[i] = (float)_cms[i].specificity();
mcc[i] = (float)_cms[i].mcc();
max_per_class_error[i] = (float)_cms[i].max_per_class_error();
}
}
public boolean toHTML( StringBuilder sb ) {
try {
if (actual_domain == null) actual_domain = new String[]{"false","true"};
// make local copies to avoid getting clear()'ed out in the middle of printing (can happen for DeepLearning, for example)
String[] my_actual_domain = actual_domain.clone();
String[] my_threshold_criteria = threshold_criteria.clone();
float[] my_threshold_for_criteria = threshold_for_criteria.clone();
float[] my_thresholds = thresholds.clone();
hex.ConfusionMatrix[] my_cms = _cms.clone();
if (my_thresholds == null) return false;
if (my_threshold_criteria == null) return false;
if (my_cms == null) return false;
if (idxCriter == null) return false;
sb.append("<div>");
DocGen.HTML.section(sb, "<a href=\"http://en.wikipedia.org/wiki/Receiver_operating_characteristic\">Scoring for Binary Classification</a>");
// data for JS
sb.append("\n<script type=\"text/javascript\">");//</script>");
sb.append("var cms = [\n");
for (hex.ConfusionMatrix cm : _cms) {
StringBuilder tmp = new StringBuilder();
cm.toHTML(tmp, my_actual_domain);
sb.append("\t'" + StringEscapeUtils.escapeJavaScript(tmp.toString()) + "',\n");
}
sb.append("];\n");
sb.append("var criterion = " + threshold_criterion.ordinal() + ";\n"); //which one
sb.append("var criteria = [");
for (String c : my_threshold_criteria) sb.append("\"" + c + "\",");
sb.append(" ];\n");
sb.append("var thresholds = [");
for (double t : my_threshold_for_criteria) sb.append((float) t + ",");
sb.append(" ];\n");
sb.append("var F1_values = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].F1() + ",");
sb.append(" ];\n");
sb.append("var F2_values = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].F2() + ",");
sb.append(" ];\n");
sb.append("var F0point5_values = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].F0point5() + ",");
sb.append(" ];\n");
sb.append("var accuracy = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].accuracy() + ",");
sb.append(" ];\n");
sb.append("var error = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].err() + ",");
sb.append(" ];\n");
sb.append("var precision = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].precision() + ",");
sb.append(" ];\n");
sb.append("var recall = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].recall() + ",");
sb.append(" ];\n");
sb.append("var specificity = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].specificity() + ",");
sb.append(" ];\n");
sb.append("var mcc = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].mcc() + ",");
sb.append(" ];\n");
sb.append("var max_per_class_error = [");
for (int i = 0; i < my_cms.length; ++i) sb.append((float) my_cms[i].max_per_class_error() + ",");
sb.append(" ];\n");
sb.append("var idxCriter = [");
for (int i : idxCriter) sb.append(i + ",");
sb.append(" ];\n");
sb.append("</script>\n");
// Selection of threshold criterion
sb.append("\n<div><b>Threshold criterion:</b></div><select id='threshold_select' onchange='set_criterion(this.value, idxCriter[this.value])'>\n");
for (int i = 0; i < my_threshold_criteria.length; ++i)
sb.append("\t<option value='" + i + "'" + (i == threshold_criterion.ordinal() ? "selected='selected'" : "") + ">" + my_threshold_criteria[i] + "</option>\n");
sb.append("</select>\n");
sb.append("</div>");
DocGen.HTML.arrayHead(sb);
sb.append("<th>AUC</th>");
sb.append("<th>Gini</th>");
sb.append("<th id='threshold_criterion'>Threshold for " + threshold_criterion.toString().replace("_", " ") + "</th>");
sb.append("<th>F1 </th>");
sb.append("<th>Accuracy </th>");
sb.append("<th>Error </th>");
sb.append("<th>Precision </th>");
sb.append("<th>Recall </th>");
sb.append("<th>Specificity</th>");
sb.append("<th>MCC</th>");
sb.append("<th>Max per class Error</th>");
sb.append("<tr class='warning'>");
sb.append("<td>" + String.format("%.5f", AUC()) + "</td>"
+ "<td>" + String.format("%.5f", Gini()) + "</td>"
+ "<td id='threshold'>" + String.format("%g", threshold()) + "</td>"
+ "<td id='F1_value'>" + String.format("%.7f", F1()) + "</td>"
+ "<td id='accuracy'>" + String.format("%.7f", accuracy()) + "</td>"
+ "<td id='error'>" + String.format("%.7f", err()) + "</td>"
+ "<td id='precision'>" + String.format("%.7f", precision()) + "</td>"
+ "<td id='recall'>" + String.format("%.7f", recall()) + "</td>"
+ "<td id='specificity'>" + String.format("%.7f", specificity()) + "</td>"
+ "<td id='mcc'>" + String.format("%.7f", mcc()) + "</td>"
+ "<td id='max_per_class_error'>" + String.format("%.7f", max_per_class_error()) + "</td>"
);
DocGen.HTML.arrayTail(sb);
// sb.append("<div id='BestConfusionMatrix'>");
// CM().toHTML(sb, actual_domain);
// sb.append("</div>");
sb.append("<table><tr><td>");
plotROC(sb);
sb.append("</td><td id='ConfusionMatrix'>");
CM().toHTML(sb, my_actual_domain);
sb.append("</td></tr>");
sb.append("<tr><td><h5>Threshold:</h5></div><select id=\"select\" onchange='show_cm(this.value)'>\n");
for (int i = 0; i < my_cms.length; ++i)
sb.append("\t<option value='" + i + "'" + (my_thresholds[i] == threshold() ? "selected='selected'" : "") + ">" + my_thresholds[i] + "</option>\n");
sb.append("</select></td></tr>");
sb.append("</td>");
sb.append("</table>");
sb.append("\n<script type=\"text/javascript\">");
sb.append("function show_cm(i){\n");
sb.append("\t" + "document.getElementById('ConfusionMatrix').innerHTML = cms[i];\n");
sb.append("\t" + "document.getElementById('F1_value').innerHTML = F1_values[i];\n");
sb.append("\t" + "document.getElementById('accuracy').innerHTML = accuracy[i];\n");
sb.append("\t" + "document.getElementById('error').innerHTML = error[i];\n");
sb.append("\t" + "document.getElementById('precision').innerHTML = precision[i];\n");
sb.append("\t" + "document.getElementById('recall').innerHTML = recall[i];\n");
sb.append("\t" + "document.getElementById('specificity').innerHTML = specificity[i];\n");
sb.append("\t" + "document.getElementById('mcc').innerHTML = mcc[i];\n");
sb.append("\t" + "document.getElementById('max_per_class_error').innerHTML = max_per_class_error[i];\n");
sb.append("\t" + "update(dataset);\n");
sb.append("}\n");
sb.append("function set_criterion(i, idx){\n");
sb.append("\t" + "criterion = i;\n");
// sb.append("\t" + "document.getElementById('BestConfusionMatrix').innerHTML = cms[idx];\n");
sb.append("\t" + "document.getElementById('threshold_criterion').innerHTML = \" Threshold for \" + criteria[i];\n");
sb.append("\t" + "document.getElementById('threshold').innerHTML = thresholds[i];\n");
sb.append("\t" + "show_cm(idx);\n");
sb.append("\t" + "document.getElementById(\"select\").selectedIndex = idx;\n");
sb.append("\t" + "update(dataset);\n");
sb.append("}\n");
sb.append("</script>\n");
return true;
} catch (Exception ex) {
return false;
}
}
public void toASCII( StringBuilder sb ) {
sb.append(CM().toString());
sb.append("AUC: " + String.format("%.5f", AUC()));
sb.append(", Gini: " + String.format("%.5f", Gini()));
sb.append(", F1: " + String.format("%.5f", F1()));
sb.append(", F2: " + String.format("%.5f", F2()));
sb.append(", F0point5: " + String.format("%.5f", F0point5()));
sb.append(", Accuracy: " + String.format("%.5f", accuracy()));
sb.append(", Error: " + String.format("%.5f", err()));
sb.append(", Precision: " + String.format("%.5f", precision()));
sb.append(", Recall: " + String.format("%.5f", recall()));
sb.append(", Specificity: " + String.format("%.5f", specificity()));
sb.append(", MCC: " + String.format("%.5f", mcc()));
sb.append(", Threshold for " + threshold_criterion.toString().replace("_", " ") + ": " + String.format("%g", threshold()));
sb.append("\n");
}
void plotROC(StringBuilder sb) {
sb.append("<script type=\"text/javascript\" src='/h2o/js/d3.v3.min.js'></script>");
sb.append("<div id=\"ROC\">");
sb.append("<style type=\"text/css\">");
sb.append(".axis path," +
".axis line {\n" +
"fill: none;\n" +
"stroke: black;\n" +
"shape-rendering: crispEdges;\n" +
"}\n" +
".axis text {\n" +
"font-family: sans-serif;\n" +
"font-size: 11px;\n" +
"}\n");
sb.append("</style>");
sb.append("<div id=\"rocCurve\" style=\"display:inline;\">");
sb.append("<script type=\"text/javascript\">");
sb.append("//Width and height\n");
sb.append("var w = 500;\n"+
"var h = 300;\n"+
"var padding = 40;\n"
);
sb.append("var dataset = [");
for(int c = 0; c < _fprs.length; c++) {
assert(_tprs.length == _fprs.length);
if (c == 0) {
sb.append("["+String.valueOf(_fprs[c])+",").append(String.valueOf(_tprs[c])).append("]");
}
sb.append(", ["+String.valueOf(_fprs[c])+",").append(String.valueOf(_tprs[c])).append("]");
}
//diagonal
for(int c = 0; c < 200; c++) {
sb.append(", ["+String.valueOf(c/200.)+",").append(String.valueOf(c/200.)).append("]");
}
sb.append("];\n");
sb.append(
"//Create scale functions\n"+
"var xScale = d3.scale.linear()\n"+
".domain([0, d3.max(dataset, function(d) { return d[0]; })])\n"+
".range([padding, w - padding * 2]);\n"+
"var yScale = d3.scale.linear()"+
".domain([0, d3.max(dataset, function(d) { return d[1]; })])\n"+
".range([h - padding, padding]);\n"+
"var rScale = d3.scale.linear()"+
".domain([0, d3.max(dataset, function(d) { return d[1]; })])\n"+
".range([2, 5]);\n"+
"//Define X axis\n"+
"var xAxis = d3.svg.axis()\n"+
".scale(xScale)\n"+
".orient(\"bottom\")\n"+
".ticks(5);\n"+
"//Define Y axis\n"+
"var yAxis = d3.svg.axis()\n"+
".scale(yScale)\n"+
".orient(\"left\")\n"+
".ticks(5);\n"+
"//Create SVG element\n"+
"var svg = d3.select(\"#rocCurve\")\n"+
".append(\"svg\")\n"+
".attr(\"width\", w)\n"+
".attr(\"height\", h);\n"+
"/*"+
"//Create labels\n"+
"svg.selectAll(\"text\")"+
".data(dataset)"+
".enter()"+
".append(\"text\")"+
".text(function(d) {"+
"return d[0] + \",\" + d[1];"+
"})"+
".attr(\"x\", function(d) {"+
"return xScale(d[0]);"+
"})"+
".attr(\"y\", function(d) {"+
"return yScale(d[1]);"+
"})"+
".attr(\"font-family\", \"sans-serif\")"+
".attr(\"font-size\", \"11px\")"+
".attr(\"fill\", \"red\");"+
"*/\n"+
"//Create X axis\n"+
"svg.append(\"g\")"+
".attr(\"class\", \"axis\")"+
".attr(\"transform\", \"translate(0,\" + (h - padding) + \")\")"+
".call(xAxis);\n"+
"//X axis label\n"+
"d3.select('#rocCurve svg')"+
".append(\"text\")"+
".attr(\"x\",w/2)"+
".attr(\"y\",h - 5)"+
".attr(\"text-anchor\", \"middle\")"+
".text(\"False Positive Rate\");\n"+
"//Create Y axis\n"+
"svg.append(\"g\")"+
".attr(\"class\", \"axis\")"+
".attr(\"transform\", \"translate(\" + padding + \",0)\")"+
".call(yAxis);\n"+
"//Y axis label\n"+
"d3.select('#rocCurve svg')"+
".append(\"text\")"+
".attr(\"x\",150)"+
".attr(\"y\",-5)"+
".attr(\"transform\", \"rotate(90)\")"+
//".attr(\"transform\", \"translate(0,\" + (h - padding) + \")\")"+
".attr(\"text-anchor\", \"middle\")"+
".text(\"True Positive Rate\");\n"+
"//Title\n"+
"d3.select('#rocCurve svg')"+
".append(\"text\")"+
".attr(\"x\",w/2)"+
".attr(\"y\",padding - 20)"+
".attr(\"text-anchor\", \"middle\")"+
".text(\"ROC\");\n" +
"function update(dataset) {" +
"svg.selectAll(\"circle\").remove();" +
"//Create circles\n"+
"var data = svg.selectAll(\"circle\")"+
".data(dataset);\n"+
"var activeIdx = idxCriter[criterion];\n" +
"data.enter()\n"+
".append(\"circle\")\n"+
".attr(\"cx\", function(d) {\n"+
"return xScale(d[0]);\n"+
"})\n"+
".attr(\"cy\", function(d) {\n"+
"return yScale(d[1]);\n"+
"})\n"+
".attr(\"fill\", function(d,i) {\n"+
" if (document.getElementById(\"select\") != null && i == document.getElementById(\"select\").selectedIndex && i != activeIdx) {\n" +
" return \"blue\"\n" +
" }\n" +
" else if (i == activeIdx) {\n"+
" return \"green\"\n"+
" }\n" +
" else if (d[0] != d[1] || d[0] == 0 || d[1] == 0) {\n"+
" return \"blue\"\n"+
" }\n" +
" else {\n"+
" return \"red\"\n"+
" }\n"+
"})\n"+
".attr(\"r\", function(d,i) {\n"+
" if (document.getElementById(\"select\") != null && i == document.getElementById(\"select\").selectedIndex && i != activeIdx) {\n" +
" return 4\n" +
" }\n" +
" else if (i == activeIdx) {\n"+
" return 6\n"+
" }\n" +
" else if (d[0] != d[1] || d[0] == 0 || d[1] == 0) {\n"+
" return 1.5\n"+
" }\n"+
" else {\n"+
" return 1\n"+
" }\n" +
"})\n" +
".on(\"mouseover\", function(d,i){\n" +
" if(i < " + _fprs.length + ") {" +
" document.getElementById(\"select\").selectedIndex = i\n" +
" show_cm(i)\n" +
" }\n" +
"});\n"+
"data.exit().remove();" +
"}\n" +
"update(dataset);");
sb.append("</script>");
sb.append("</div>");
}
}