1var isDoingSelect=0; 2var delayUpdateToolip; 3 4/* 5 Add sponsor circle 6*/ 7function addSponsorCircle(oc,dat) { 8 var scaleX = d3.scale.linear().domain([-1,1]).range([oc.margins()['left'],oc.width()-oc.margins()['right']]); 9 var scaleY = d3.scale.linear().domain([-1,1]).range([oc.height()-oc.margins()['bottom'],oc.margins()['top']]); 10 var vc = globalData.rollcalls[0]; 11 for (var i = 0, len = vc.votes.length; i < len; i++) { 12 if (vc.votes[i].icpsr == vc.sponsor) break; 13 } 14 if (i<len) { 15 var ocSVG = d3.select(oc.g()[0][0]), sponsor = vc.votes[i]; 16 ocSVG.append("circle") 17 .attr("stroke",blendColors([sponsor],false)) 18 .attr("stroke-width","1px") 19 .attr("fill","none") 20 .attr("r", 9) 21 .attr("cx", scaleX(sponsor.x)) 22 .attr("cy", scaleY(sponsor.y)+1.5); // JBL: Not sure why we need the extra 1.5 here... 23 } 24} 25 26 27/* 28 Draws the background circles, labels and text for the scatter chart 29*/ 30function decorateNominate(oc,data) { 31 var scmargins = oc.margins() 32 var width = oc.width(); 33 var height = oc.height(); 34 var margin = scmargins['left']-scmargins['right']; 35 var marginCircle = scmargins['right']; // Distance of the main circle to the axis 36 var tickLength = 15; 37 38 // Calculate ellipse attrs 39 var radiusX = (width - margin)/2 - marginCircle; 40 var radiusY = nomDWeight*radiusX; 41 var circleCenter = { "x": (width + margin)/2, "y": (height - margin)/2 }; 42 43 // Select the base SVG 44 var ocSVG = d3.select(oc.g()[0][0]); 45 46 // Remove everything that's an axis. 47 ocSVG.selectAll(".axis").remove(); 48 49 // Place bg stuff in SVG tree in front of .chart-body scatter points 50 var svgbg = ocSVG.insert("g",".chart-body"); 51 var gg = svgbg.append("g").attr("id","scatter-background"); 52 53 // X-axis 54 var xAxisMin = circleCenter.x - radiusX; 55 var xAxisMax = circleCenter.x + radiusX; 56 var xAxisLen = xAxisMax - xAxisMin; 57 var yDimPos = circleCenter.y + radiusY + 10; 58 59 gg.append('polyline') 60 .attr("class", "scatter-axis") 61 .attr("points", sprintf("%d,%d %d,%d %d,%d %d,%d", 62 xAxisMin, yDimPos + tickLength, 63 xAxisMin, yDimPos, 64 xAxisMax, yDimPos, 65 xAxisMax, yDimPos + tickLength)); 66 67 gg.append('text') 68 .text("Liberal") 69 .attr("x", xAxisMin + 0.02 * xAxisLen) 70 .attr("y", yDimPos + 20) 71 .attr("style","text-anchor: start; fill: #808080;"); 72 gg.append('text') 73 .text("Conservative") 74 .attr("x", xAxisMin + 0.98 * xAxisLen) 75 .attr("y", yDimPos + 20) 76 .attr("style","text-anchor: end; fill: #808080;"); 77 gg.append('text') 78 .text("DW-Nominate Dimension 1: Economic/Redistributive") 79 .attr("x", xAxisMin + xAxisLen/2).attr("y", yDimPos + 20) 80 .attr("style","text-anchor:middle"); 81 // End X axis. 82 83 // Y-axis 84 var yAxisMin = circleCenter.y - radiusY; 85 var yAxisMax = circleCenter.y + radiusY; 86 var yAxisLen = yAxisMax - yAxisMin; 87 88 gg.append('polyline') 89 .attr("class","scatter-axis") 90 .attr("points", sprintf("%d,%d %d,%d %d,%d %d,%d", 91 margin, yAxisMin, 92 margin + tickLength, yAxisMin, 93 margin + tickLength, yAxisMax, 94 margin, yAxisMax)); 95 gg.append('text') 96 .text("Conservative") 97 .attr("x", 50) 98 .attr("y", yAxisMin + 0.05 * yAxisLen) 99 .attr("style","text-anchor: end; fill: #808080;") 100 .attr("transform", sprintf("rotate(-90 50 %d)", yAxisMin + 0.05 * yAxisLen)); 101 gg.append('text') 102 .text("Liberal") 103 .attr("x", 50) 104 .attr("y", yAxisMin + 0.95 * yAxisLen) 105 .attr("style","text-anchor: start; fill: #808080;") 106 .attr("transform", sprintf("rotate(-90 50 %d)", yAxisMin + 0.95 * yAxisLen)); 107 108 var seconddim_label = "NOMINATE Dimension 2: Other Votes"; 109 if(congressNum != null) 110 { 111 switch(true) { 112 case (congressNum <= 24): 113 break; 114 case (congressNum <= 32): 115 seconddim_label = "NOMINATE Dimension 2: Slavery / Racial"; 116 break; 117 default: 118 break; 119 } 120 } 121 122 gg.append('text') 123 .text(seconddim_label) 124 .attr("x", 30) 125 .attr("y", yAxisMin + yAxisLen/2) 126 .attr("style","text-anchor:middle")
127 .attr("transform", sprintf("rotate(-90 30 %d)", yAxisMin + yAxisLen/2)); 128 // End Y axis 129 130 // Add yea and nay locations to the plot on top of the dots 131 // Problem is that with Y/N on top we can't select point under/near the Y/N 132 // Need a way to insert after the dots but before the brush. Putting the Y/N group right 133 // before the brush group does it. --JBL 134 135 var ggg = ocSVG.insert("g",".brush"); 136 137 var hasNominate = 1; 138 if (data.rollcalls == undefined || data.rollcalls[0].nominate == undefined || data.rollcalls[0].nominate.spread == undefined) 139 { 140 console.log("Vote type 1: Missing critical data / rollcalls, nominate, or spread."); 141 hasNominate=0; 142 gg 143 .append("ellipse") 144 .attr("stroke","black") 145 .attr("stroke-width","1px") 146 .attr("fill","none") 147 .attr("rx", radiusX) 148 .attr("ry", radiusY) 149 .attr("cx", circleCenter.x) 150 .attr("cy", circleCenter.y); 151 var voteShare=0; 152 } 153 else 154 { 155 if(data.rollcalls[0].congress==0) var vn = data.rollcalls[0].nominate.imputed; 156 else var vn = data.rollcalls[0].nominate; 157 var voteShare = Math.max(data.rollcalls[0].yea, data.rollcalls[0].nay)/(data.rollcalls[0].yea+data.rollcalls[0].nay); 158 } 159 160 161 // Fit box regardless if cutline exists 162 if(vn != undefined && vn.pre != undefined && vn.pre != "") 163 { 164 ggg.append('text').text(sprintf("Proportionate Reduction in Error (PRE): %4.2f", (vn.pre == null || isNaN(vn.pre) || vn.pre=="") ? 0 : vn.pre)) 165 .attr("class", "fitbox") 166 .attr("x", xAxisMin + 0.98 * xAxisLen) 167 .attr("style","text-anchor: end;") 168 .attr("y", yDimPos + 40); 169 ggg.append('text').text(sprintf("Proportion of votes correctly classified: %4.2f", (vn.classified == null || isNaN(vn.classified) || vn.classified=="") ? 0 : vn.classified )) 170 .attr("class", "fitbox") 171 .attr("x", xAxisMin + 0.02 * xAxisLen) 172 .attr("y", yDimPos + 40); 173 } 174 175 var legendType=1; 176 177 178 var doHM = 1; 179 180 if (hasNominate && (vn.spread[0]!=0 | vn.spread[1]!=0) && (voteShare<=0.975 | data.rollcalls[0].nominate.pre > 0) ) { // Only drawn if there is a cutline! 181 console.log("Vote type 2: Has actual division"); 182 var hmTranslate = {x: scmargins['left'], y: (oc.height()/2) - radiusY - scmargins['top']}; 183 var gggg = gg.append("g") 184 .attr("id","heat-map") 185 .attr("transform","translate(" + hmTranslate.x + "," + hmTranslate.y+ ")"); 186 187 nominateHeatmap(gggg, vn.mid[0], vn.mid[1], vn.spread[0], vn.spread[1], 188 nomBeta, nomDWeight, 2 * radiusX, 2 * radiusY, 30, ["#FFFFFF","#FFFFAA"]); 189 190 // Calculate where the YN text axis goes. 191 function closestpt(vn) { 192 var b = vn.slope; 193 var angle = Math.atan( (vn.spread[1] * nomDWeight) / (vn.spread[0]) ); 194 var a = vn.mid[1] - b * vn.mid[0]; 195 var xstar = -b * a / (1 + b * b); 196 var obj = { 197 angle: angle, 198 b: b, 199 a: a, 200 xstar: xstar, 201 ystar: b * xstar + a, 202 offsetX: 0.05 * Math.cos(angle) * Math.sign(vn.spread[0]), 203 offsetY: (vn.slope > 0 ? 1 : -1) * 0.05 * Math.sin(angle) * Math.sign(vn.spread[1]) / nomDWeight 204 } 205 return(obj); 206 } 207 208 var ynp = closestpt(vn); 209 210 var ynpts = [circleCenter.x + radiusX*(ynp.xstar+ynp.offsetX), 211 circleCenter.y - radiusY*(ynp.ystar-ynp.offsetY), 212 circleCenter.x + radiusX*(ynp.xstar-ynp.offsetX), 213 circleCenter.y - radiusY*(ynp.ystar+ynp.offsetY)]; 214 215 var angle = 57.29578*ynp.angle; 216 217 // This is a hack based on what quadrant the text angle is in. 218 var cs = (angle>0?1:0) + 2*(vn.spread[0]>0?1:0); 219 switch(cs) 220 { 221 case 0: angle = 90-angle; break; 222 case 1: angle = 90-angle; break; 223 case 2: angle = 270-angle; break; 224 case 3: angle = -90-angle; break; 225 } 226 227 // Plot the Y text using ynpts[2], [3] and rotating by the angle above. 228 ggg.append('text').text('Y') 229 .attr("class","yeanay") 230 .attr("x", ynpts[2]) 231 .attr("y", ynpts[3]) 232 .attr("transform",sprintf("rotate(%d %d %d)", angle, ynpts[2], ynpts[3])); 233
234 // Plot the N text using ynpts[0], [1], and rotating by the angle above + 180 235 ggg.append('text').text('N') 236 .attr("class","yeanay") 237 .attr("x", ynpts[0]) 238 .attr("y", ynpts[1]) 239 .attr("transform",sprintf("rotate(%d %d %d)", 180+angle, ynpts[0], ynpts[1])); 240 241 } 242 else 243 { 244 if(data.rollcalls != undefined) 245 { 246 var nomData = data.rollcalls[0].nominate; 247 if(nomData != undefined && nomData.intercept != undefined && nomData.pre === "") 248 { 249 console.log("Vote type 3: PRE is undefined but NOMINATE was estimated."); 250 // This is actually the case where NOMINATE is estimated but the PRE/probability stuff is not. 251 ggg.append('text').text("NOMINATE not") 252 .attr("class","fitbox").attr("x", xAxisMax - 110) 253 .attr("y", yAxisMax - 12); 254 ggg.append('text').text("yet estimated") 255 .attr("class","fitbox").attr("x", xAxisMax - 110) 256 .attr("y", yAxisMax - 0); 257 doHM = 0; 258 } 259 else if(nomData != undefined && nomData.spread != undefined) 260 { 261 console.log("Vote type 4: Lopsided vote."); 262 ggg.append('text').text("Lopsided vote with") 263 .attr("class","fitbox").attr("x", xAxisMax - 110) 264 .attr("y", yAxisMax - 12); 265 ggg.append('text').text("no ideological division") 266 .attr("class","fitbox").attr("x", xAxisMax - 110) 267 .attr("y", yAxisMax - 0);
268 var legendType=2; 269 } 270 else 271 { 272 console.log("Vote type 5: NOMINATE actually not estimated."); 273 ggg.append('text').text("NOMINATE not") 274 .attr("class","fitbox").attr("x", xAxisMax - 110) 275 .attr("y", yAxisMax - 12); 276 ggg.append('text').text("yet estimated") 277 .attr("class","fitbox").attr("x", xAxisMax - 110) 278 .attr("y", yAxisMax - 0); 279 var legendType=3; 280 } 281 282 283 if(doHM) 284 { 285 var hmTranslate = {x: scmargins['left'], y: (oc.height() / 2) - radiusY - scmargins['top']}; 286 var gggg = gg.append("g") 287 .attr("id", "heat-map") 288 .attr("transform", "translate(" + hmTranslate.x + "," + hmTranslate.y + ")"); 289 290 var pctYea = globalData.rollcalls[0].yea_count / (globalData.rollcalls[0].yea_count + globalData.rollcalls[0].nay_count); 291 lopsidedHeatmap(gggg, nomDWeight, 2*radiusX, 2*radiusY, ["#FFFFFF","#ffffcc"], pctYea); 292 } 293 } 294 } 295 296 // Tooltip for Prob(yea) 297 if (hasNominate==1 && doHM) { //Show it as long as nominate has been fit 298 299 // Collecting info to find vote prob for heat map 300 if(!$("#hm_tooltip").length) // Make sure we don't already have a tooltip 301 { 302 $("<div></div>") 303 .attr("id","hm_tooltip") 304 .addClass("d3-tip") 305 .addClass("heatmapTip") 306 .appendTo(document.body); 307 } 308 var yunits = radiusY; 309 var xunits = radiusX; 310 var brush = ocSVG.select(".brush"); 311 brush.on("mouseover",function() { if(!isDoingSelect) { $("#hm_tooltip").css("visibility","visible"); }}); 312 brush.on("mousemove",function() { 313 var ypdiv = $("#hm_tooltip"); 314 var cx = circleCenter.x-hmTranslate.x, 315 cy = circleCenter.y-hmTranslate.y, 316 x = (d3.mouse(this)[0] - cx)/xunits, 317 y = -(d3.mouse(this)[1] - cy)/yunits; 318 if (vn.spread[0]!=0 & vn.spread[1]!=0) { 319 // Votes with cutlines... 320 var yeaProb = nomProbYea(x,y,vn.mid[0],vn.mid[1],vn.spread[0], 321 vn.spread[1],nomDWeight,nomBeta); 322 } else { 323 // Lopsided vote case... 324 var yeaProb = data.rollcalls[0].yea_count/(data.rollcalls[0].yea_count+data.rollcalls[0].nay_count); 325 } 326 if ((x*x+y*y)<1.0) { 327 if(yeaProb>0.99) { 328 dispProb = " > 0.99" 329 } else if(yeaProb<0.01) { 330 dispProb = " < 0.01" 331 } else { 332 dispProb = Math.round(100*yeaProb)/100; 333 } 334 if(!isDoingSelect) ypdiv.css("visibility","visible"); 335 ypdiv.html("<strong>Probability of Voting Yea: </strong>" + dispProb + "<br/> The calculated probability that a member at this position would vote 'Yea' on the rollcall.") 336 .css("left",d3.event.pageX + 20 + "px") 337 .css("top",(d3.event.pageY - 48) + "px"); 338 } 339 else 340 { 341 // Cursor is on nominate plot, but not in the Oval 342 ypdiv.css("visibility","hidden"); 343 } 344 345 }); 346 // Don't show tooltip if user is making a range selection 347 brush.on("mousedown",function() { $("#hm_tooltip").css("visibility","hidden"); isDoingSelect=1; }); 348 brush.on("mouseup",function() { $("#hm_tooltip").css("visibility","visible"); isDoingSelect=0; }); 349 brush.on("mouseout", function() { $("#hm_tooltip").css("visibility","hidden"); }); 350 }; 351} 352
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.