1var familytree = (function() { 2 var svg, link = {}, node; 3 var zoom; 4 var width = 1200, height = 900; 5 var container; 6 var eventNames = ["node_click", "node_dblclick", "node_contextmenu", "graph_init", "force_stage", "force_stop"], 7 events = d3.dispatch.apply(null, eventNames), 8 markerEnds = d3.range(1, 9).reduce(function(m, d) { 9 return (m[d] = "url(#end" + d + ")", m) 10 }, {}), 11 phasesAlpha = (function(){ 12 var p = [0.08, 0.04, 0.02, 0], i = null; 13 return function (alpha){ 14 // resets if alpha is falsey or > p[0] 15 var i0 = i; 16 i = alpha ? (alpha > p[0] ? (i = 0) : i) : 0; 17 if(alpha < (p[i])) i++; 18 return i != i0; 19 }; 20 })(), // fire the force_stage event when alpha reaches each value 21 22 fdg = (function() { 23 var force = d3.layout.force() 24 .size([width, height]) 25 .gravity(.2) 26 .charge(-400) 27 .friction(0.9) 28 .theta(0.9) 29 .linkStrength(1) 30 .distance(100) 31 .on("tick", tick) 32 .on("end", events.force_stop), 33 fdg = {}; 34 35 svg = zoomableSVG({width: "100%", height: "100%"}, "#familytreecontentsvg", {extent: [0.4, 4], dblclk: null}); 36 37 function data(dataSet) { 38 if (!dataSet) return force.nodes().length ? {nodes: force.nodes(), links: force.links()} : null; 39 force 40 .nodes(dataSet.nodes) 41 .links(dataSet.links); 42 43 svg.onZoom(zoomed); 44 45 hookDrag(force.drag(), "dragstart.force", function(d) { 46 //prevent dragging on the nodes from dragging the canvas 47 var e = d3.event.sourceEvent; 48 e.preventDefault(); 49 if(e.button != 2) { 50 //left click only 51 e.stopPropagation(); 52 d.fixed = e.shiftKey || e.touches && (e.touches.length > 1); 53 events.node_click(d); 54 } 55 }); 56 hookDrag(force.drag(), "dragend.force", function(d) { 57 //prevent dragging on the nodes from dragging the canvas 58 var e = d3.event.sourceEvent; 59 d.fixed = e.shiftKey || d.fixed; 60 }); 61 62 var varsvgMarker = svg.selectAll("defs") 63 .data([["end"]]) 64 .enter().append("defs") 65 .selectAll("marker") 66 .data(id) 67 .enter(); 68 createMarker(varsvgMarker); 69 70 container = svg.container("#container").data([{nodes: [force.nodes()], links: [force.links()]}]); 71 container.enter().append("g").attr("id", "container"); 72 73 var links = container.selectAll(".links").data(function(d) { 74 return d.links 75 }); 76 links.enter().append("g").attr("class", "links"); 77 78 link = links.selectAll("line").data(id); 79 link.enter().append("line"); 80 link.exit().remove(); 81 link.attr("class", function(d) { 82 if(d.relation == "BEGETS") { 83 return "linkBEGETS"; 84 } 85 if(d.relation == "LOVES") { 86 return "linkLOVES"; 87 } 88 if(d.relation == "HASSIBLING") { 89 return "linkHASSIBLING"; 90 } 91 }) 92 .attr("marker-end", function(d) { 93 if(d.relation == "BEGETS") { 94 return markerEnds[d.targetSign] || "url(#end1)" 95 } 96 }); 97 98 var nodes = container.selectAll(".nodes").data(function(d) { 99 return d.nodes 100 }); 101 nodes.enter().append("g").attr("class", "nodes"); 102 103 node = nodes.selectAll(".node").data(id, nodeKey); 104 var newNode = node.enter().append("g").attr("class", "node"); 105 node.exit().remove(); 106 newNode
107 .on("mouseover", mouseover) 108 .on("mouseout", mouseout) 109 .on("dblclick", function(d) { 110 events.node_dblclick(d); 111 }) 112 .on('contextmenu', function(data, index) { 113 d3.event.preventDefault(); 114 events.node_contextmenu(data, index); 115 }) 116 .call(force.drag); 117 newNode 118 .append("circle") 119 .attr("class", "bgcircle"); 120 node.select("circle") 121 .attr("r", function(d) { 122 return Math.abs(posXY(d)); 123 }) 124 .style("fill", function(d) { 125 return colourRace(d); 126 }) 127 .style("stroke", function(d) { 128 return colourRace(d); 129 }); 130 newNode 131 .append("svg:image")
132 .attr("class", "circle") 133 .attr("xlink:href", imgHref) 134 .attr("width", function(d) { 135 return sizeXY(d); 136 }) 137 .attr("height", function(d) { 138 return sizeXY(d); 139 }) 140 .on("error", function() { 141 d3.select(this).style("visibility", "hidden"); 142 }); 143 node.select("image") 144 .attr("x", function(d) { 145 return posXY(d); 146 }) 147 .attr("y", function(d) { 148 return posXY(d); 149 }); 150 newNode 151 .append("text") 152 .attr("class", "nodetext"); 153 node.select("text") 154 .attr("x", function(d) { 155 return Math.abs(posXY(d) - 5); 156 }) 157 .attr("y", 4) 158 .text(function(d) { 159 return d.name; 160 }); 161 162 force.start(); 163 164 function hookDrag(target, event, hook) { 165 //hook force.drag behaviour 166 var stdDrag = target.on(event); 167 target.on(event, function(d) { 168 hook.call(this, d); 169 stdDrag.call(this, d); 170 }); 171 } 172 173 function zoomed() { 174 if(force.alpha() < 0.01) force.alpha(0.01); 175 } 176 177 function imgHref(d) { 178 return "/pics/arda/creature/" + d.uniquename + "_familytree.png"; 179 } 180 181 function nodeKey(d, i) { 182 return this.href ? d3.select(this).attr("href") : imgHref(d); 183 } 184 185 // zoom context services 186 // content is the target for zoom movements in zoomed 187 d3.rebind(fdg, container, "attr"); 188 189 // access the current transform state in zoom listener coordinates 190 d3.rebind(fdg, svg, "translate"); 191 192 events.graph_init(); 193 194 return this; 195 196 } 197 198 var zoomTo = svg.zoomTo.bind(null, 1000); 199 fdg.zoomTo = (function() { 200 var _n; 201 return function(n) { 202 if(!n && ! _n) return false; 203 zoomTo(n || _n); 204 _n = n ? n : _n; 205 } 206 })(); 207 fdg.zoomTime = (function() { 208 var _t; 209 return function(t) { 210 if(t == undefined) return _t; 211 if(t == null) return (zoomTo = svg.zoomTo, this); 212 zoomTo = svg.zoomTo.bind(null, _t = t); 213 return this; 214 } 215 })(); 216 217 fdg.focusNode = (function() { 218 var _datum; // previous datum is stored as default 219 return function(datum) { 220 _datum = datum || _datum; 221 // closure on a reference to the node and transition state 222 var _n = node.filter(function(d) { 223 return _datum === d; 224 }), _trans, _t; 225 // return an object with chainable methods 226 return { 227 highlight: function() { 228 _trans = highlight(_n); 229 return this; 230 }, 231 delay : function(t) { 232 _t = t; 233 return this; 234 }, 235 blur : function() { 236 if(_trans) _trans.each("end.is-focused", function() { 237 blur(_n, _t) 238 }); 239 else blur(_n); 240 return this; 241 } 242 } 243 } 244 })(); 245 246 fdg.data = data; 247 248 return fdg; 249 250 function tick(e) { 251 link 252 .attr("x1", function(d) { 253 return d.source.x; 254 }) 255 .attr("y1", function(d) { 256 return d.source.y; 257 }) 258 .attr("x2", function(d) { 259 return d.target.x; 260 }) 261 .attr("y2", function(d) { 262 return d.target.y; 263 }); 264 node 265 .attr("transform", function(d) { 266 return "translate(" + d.x + "," + d.y + ")"; 267 }); 268 if(phasesAlpha(e.alpha)) events.force_stage(e.alpha); 269 } 270 })().zoomTime(1000); 271 //events.on("force_stage.debug", function(a){console.log(a)}); 272 273 function mouseover() { 274 highlight(d3.select(this)) 275 } 276 function mouseout() { 277 blur(d3.select(this)); 278 } 279 280 function highlight(selection){ 281 var s = svg.scale(), transition = selection.select("text").transition() 282 .duration(750) 283 .style({"font-size": (s > 1 ? 15 : (15/s).toFixed()) + "px","fill": "#F9D276"}); 284 selection.moveToFront(); 285 return transition; 286 } 287 function blur(selection, delay){ 288 selection.select("text").transition() 289 .duration(750) 290 .delay(delay || 0) 291 .style({"font-size": "8px", "fill": "#ccc", stroke: "none"}); 292 } 293 function createMarker(svg) {
294 var obj = [38, 43, 50, 54, 60, 65, 70, 80, 85]; 295 for(var i = 0; i < obj.length; i++) { 296 svg.append("svg:marker") 297 .attr("id", "end" + (i + 1)) 298 .attr("viewBox", "0 -5 10 10") 299 .attr("refX", obj[i]) 300 .attr("refY", -0.05) 301 .attr("markerWidth", 6) 302 .attr("markerHeight", 6) 303 .attr("orient", "auto") 304 .append("svg:path") 305 .attr("d", "M0,-4L10,0L0,4"); 306 } 307 } 308 function sizeXY(d) { 309 var deflt = -10; 310 return [deflt, 20, 24, 28, 32, 36, 40, 44, 48, 52][d.significance || 0] || deflt; 311 } 312 function posXY(d) { 313 var deflt = 10; 314 return [deflt, -10, -12, -14, -16, -18, -20, -22, -24, -26][d.significance || 0] || deflt; 315 } 316 function colourRace(d) { 317 return { 318 "Ainu" : "#000", 319 "Arnorian" : "#5D8AA8", 320 "Balrog" : "#000", 321 "Dragon" : "#900", 322 "Dwarf" : "#996515", 323 "Elf" : "#900020", 324 "Ent" : "#5b3", 325 "Falmar/Falas Elf": "#0099CC", 326 "God" : "#fff", 327 "Gondorian" : "#393939", 328 "Half-Elf" : "#900020", 329 "Hobbit" : "#006600", 330 "Maia" : "#9A03B5", 331 "Man" : "#993D00", 332 "Nando" : "#355E3B", 333 "Nazgûl" : "#000", 334 "Noldo" : "#090A67", 335 "Númenórean" : "#007BA7", 336 "Orc" : "#736326", 337 "Rohir" : "#80461B", 338 "Sinda" : "#949494", 339 "Spider" : "#000", 340 "Teleri" : "#4B0101", 341 "Tree" : "#2b6", 342 "Troll" : "#000", 343 "Vala" : "#440D60", 344 "Vanya" : "#FFCC00", 345 "Werewolf" : "#000" 346 }[d.race] || "#aaa"; 347 } 348 349 return d3.rebind.bind(fdg, { 350 initializeGraph: fdg.data, 351 zoomTo: function(){ 352 fdg.zoomTo.apply(this, arguments) 353 }, 354 focusNode : fdg.focusNode, 355 events : events, 356 data: function() { 357 return fdg.data(); 358 } 359 }, events, "on").apply(fdg, events); 360 361 function zoomableSVG(size, selector, z) { 362 //delivers an svg background with zoom/drag context in the selector element 363 //if height or width is NaN, assume it is percentage and ignore margin 364 var margin = size.margin || {top: 0, right: 0, bottom: 0, left: 0}, 365 percentW = isNaN(size.width), percentH = isNaN(size.height), 366 w = percentW ? size.width : size.width - margin.left - margin.right, 367 h = percentH ? size.height : size.height - margin.top - margin.bottom, 368 zoomStart = function() { 369 return this 370 }, 371 zoomed = function() { 372 return this 373 }, 374 container, 375 376 zoom = zoom || d3.behavior.zoom().scaleExtent(z && z.extent || [0.4, 4]) 377 .on("zoom", function(d, i, j) { 378 onZoom.call(this, d, i, j); 379 zoomed.call(this, d, i, j); 380 }) 381 .on("zoomstart", function(d, i, j){ 382 onZoomStart.call(this, d, i, j); 383 zoomStart.call(this, d, i, j); 384 }); 385 386 var svg = d3.select(selector).selectAll("svg").data([["transform root"]]); 387 svg.enter().append("svg"); 388 svg.attr({width: size.width, height: size.height}); 389 390 var g = svg.selectAll("#zoom").data(id), 391 gEnter = g.enter().append("g") 392 .attr("transform", "translate(" + margin.left + "," + margin.top + ")") 393 .call(zoom) 394 .attr({class: "outline", id: "zoom"}), 395 surface = gEnter.append("rect") 396 .attr({width: w, height: h, fill: "none"}) 397 .style({"pointer-events": "all"}); 398 if(z && (typeof z.dblclk != "undefined")) gEnter.on("dblclick.zoom", z.dblclk); 399 400 function onZoomStart(){ 401 // zoom translate and scale are initially [0,0] and 1 402 // this needs to be aligned with the container to stop 403 // jump back to zero before first jump transition 404 if(container != undefined) { 405 var t = d3.transform(container.attr("transform")); 406 zoom.translate(t.translate); zoom.scale(t.scale[0]); 407 } 408 } 409 410 function onZoom(){ 411 if(container != undefined) { 412 var e = d3.event.sourceEvent, 413 isWheel = e && ((e.type == "mousewheel") || (e.type == "wheel")), 414 t = d3.transform(container.attr("transform")); 415 t.translate = d3.event.translate; t.scale = [d3.event.scale, d3.event.scale]; 416 return isWheel ? zoomWheel.call(this, t) : zoomInst.call(this, t) 417 } 418 } 419 function zoomInst(t){ 420 container.attr("transform", t.toString()); 421 } 422 function zoomWheel(t){ 423 container.transition().duration(450).attr("transform", t.toString()); 424 } 425 426 g.h = h; 427 g.w = w; 428 429 g.container = function(selection){ 430 var d3_data, d3_datum; 431 if(selection) { 432 container = g.selectAll(selection); 433 // temporarily subclass container 434 d3_data = container.data; 435 // need a reference to the update selection 436 // so force data methods back to here 437 container.data = function() { 438 delete container.data; // remove the sub-classing 439 return container = d3_data.apply(container, arguments) 440 } 441 } 442 return container; 443 }; 444 445 g.onZoom = function(cb) { 446 zoomed = cb; 447 }; 448 g.onZoomStart = function(cb) { 449 zoomStart = cb; 450 }; 451 g.zoomTo = function(t, p){ 452 // map p to the center of the plot surface 453 var s = zoom.scale(), bBox = surface.node().getBBox(), 454 w = bBox.width - infoState.width(), h = bBox.height, 455 p1 = [w/2 - p.x * s, h/2 - p.y * s]; 456 container.transition().duration(t).call(zoom.translate(p1).event); 457 }; 458 d3.rebind(g, zoom, "translate"); 459 d3.rebind(g, zoom, "scale"); 460 461 return g; 462 } 463 464 function id(d) { 465 return d; 466 } 467})(); 468 469d3.selection.prototype.moveToFront = function() { 470 return this.each(function() { 471 this.parentNode.appendChild(this); 472 });
473};
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.