8 This component displays a pedigree
12 David Lyon <dal333@cornell.edu>
13 Jeremy Edwards <jde22@cornell.edu>
21 $stock_id - the id of the stock for which pedigree information will be displayed
44 <div class="modal fade" id="add_parent_dialog" name="add_parent_dialog" tabindex="-1" role="dialog" aria-labelledby="addParentDialog">
45 <div class="modal-dialog" role="document">
46 <div class="modal-content">
47 <div class="modal-header" style="text-align: center">
48 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
49 <h4 class="modal-title" id="addParentDialog">Add Parent</h4>
51 <div class="modal-body">
52 <div class="container-fluid">
54 <form class="form-horizontal" role="form" method="post" id="add_parent_dialog_form" name="add_parent_dialog_form">
55 <div class="form-group">
56 <label class="col-sm-3 control-label">Stock Name: </label>
57 <div class="col-sm-9" >
58 <input name="stock_autocomplete" id="stock_autocomplete" class="form-control" type="text" />
61 <div class="form-group">
62 <label class="col-sm-3 control-label">Parent is: </label>
63 <div class="col-sm-9" >
64 <input type="radio" id="female" name="parent_type" value="female" checked="1" /> female<br />
65 <input type="radio" id="male" name="parent_type" value="male" /> male<br />
68 <div class="form-group" id="add_parent_cross_type_div">
69 <label class="col-sm-3 control-label">Cross Type: </label>
70 <div class="col-sm-9" >
71 <select class="form-control" id="add_parent_cross_type">
72 <option value="biparental">biparental</option>
73 <option value="self">self</option>
74 <option value="open">open pollinated</option>
75 <option value="sib">sib</option>
76 <option value="reselected">reselected</option>
77 <option value="bulk">bulk</option>
78 <option value="bulk_self">bulk selfed</option>
79 <option value="bulk_open">bulk and open pollinated</option>
80 <option value="doubled_haploid">doubled haploid</option>
81 <option value="dihaploid_induction">dihaploid induction</option>
82 <option value="polycross">polycross</option>
83 <option value="reciprocal">reciprocal</option>
84 <option value="multicross">multicross</option>
92 <div class="modal-footer">
93 <button id="close_add_parent_dialog" type="button" class="btn btn-default" data-dismiss="modal">Close</button>
94 <button type="button" class="btn btn-primary" name="add_parent_submit" id="add_parent_submit" title="Save Parent">Save</button>
102 jQuery('#add_parent_submit').click(function(){
106 jQuery("#add_parent_link").click( function () {
107 jQuery("#add_parent_dialog" ).modal("show");
110 jQuery("#stock_autocomplete").autocomplete({
111 source: '/ajax/stock/accession_autocomplete'
114 jQuery('input:radio[name="parent_type"]').change(function(){
115 var type = jQuery("input:radio[name='parent_type']:checked").val();
116 if(type == 'female'){
117 jQuery('#add_parent_cross_type_div').show();
119 jQuery('#add_parent_cross_type_div').hide();
120 jQuery('#add_parent_cross_type').val('');
124 function associate_parent() {
126 parentType = jQuery("#add_parent_dialog").find("input:checked").val();
127 //alert("PARENTTYPE="+parentType);
128 var parentName = jQuery("#stock_autocomplete").val();
129 //alert("Parent name = "+parentName);
131 if (!parentName) { alert("We need a name here, sorry!"); return; }
133 var cross_type = jQuery('#add_parent_cross_type').val();
136 var stock_id = "<% $stock_id %>";
138 url: '/ajax/stock/add_stock_parent',
142 data: 'stock_id='+stock_id+'&parent_name='+parentName+'&parent_type='+parentType+'&cross_type='+cross_type,
143 error: function(response) {
144 alert("An error occurred. Please try again later!"+response);
146 parseerror: function(response) {
147 alert("A parse error occurred. Please try again."+response);
149 success: function(response) {
150 if (response.error) { alert(response.error); }
152 alert("The parent has been added. ["+response.error+"]");
153 jQuery("#add_parent_dialog").modal("hide");
154 document.location.reload(); // reload the entire page, because pedigree info is in several places.
165 <& /util/import_javascript.mas, classes => [ 'jqueryui' ] &>
169 <span style="white-space:nowrap;">
170 <span class="glyphicon glyphicon-move" aria-hidden="true">
173 <span style="white-space:nowrap;">
174 <span class="glyphicon glyphicon-zoom-in" aria-hidden="true">
175 </span> Scroll to Zoom
177 <span style="white-space:nowrap;">
178 <span class="glyphicon glyphicon-minus" style="color:red;" aria-hidden="true">
179 </span> Female Parent
181 <span style="white-space:nowrap;">
182 <span class="glyphicon glyphicon-minus" style="color:blue;" aria-hidden="true">
185 <span style="white-space:nowrap;">
186 <span class="glyphicon glyphicon-circle-arrow-right" style="color:purple;" aria-hidden="true">
187 </span> Expand Pedigree
192 <div id="pdgv-loading-indicator">
194 Loading... <img src="/documents/img/spinner.gif" />
198 <div id="pdgv-wrap" style="border:solid thin #ddd;width:100%;"></div>
200 <!-- <div style="border:solid thin black; padding:1em; margin-top:0.25em;">
201 <label class="checkbox-inline" style="display:inline-block;">
202 <input type="checkbox" id="inlineCheckbox1" value="option1"> Enable Overlay
204 <select class="form-control" style="max-width:12em; display:inline-block;">
205 <option selected disabled>Select Trait Type</option>
206 <option>Nominal/Catagorical</option>
207 <option>Scale/Numeric</option>
209 <select class="form-control" style="max-width:10em; display:inline-block;">
210 <option selected disabled>Select Trait</option>
214 <& /util/import_javascript.mas, classes => [ 'd3.d3v4Min', 'd3.d3-pedigree-tree', 'brapi.BrAPI', 'brapi.PedigreeViewer' ] &>
215 <script type="text/javascript">
218 // This function initializes and draws a Pedigree Viewer for a specified stock ID.
219 // The Pedigree Viewer is configured with a base URL, an authentication token (if login is required),
220 // and a callback function to generate links for each node in the viewer.
221 function drawPedigreeViewer() {
223 var STOCK_ID = "<% $stock_id %>";
224 var base_url="/brapi/v2";
226 var require_login = "<% $c->get_conf('brapi_require_login') %>";
227 if (require_login === '1'){
228 auth_token = "<% CXGN::Login->new($c->dbc->dbh)->get_login_cookie() %>";
230 alert("Login required to display pedigree");
234 var pdg = PedigreeViewer(base_url,auth_token,'v2.0',function(dbId){
235 return "/stock/"+dbId+"/view";
238 pdg.newTree(STOCK_ID,function(){
239 pdg.drawViewer("#pdgv-wrap",600,600);
240 jQuery("#pdgv-loading-indicator").hide();
244 var onSwitchElement = document.getElementById('stock_pedigree_section_onswitch');
245 onSwitchElement.addEventListener('click', onSwitchHandler);
247 // display the viewer automatically - if the section is expanded by default
248 if ( onSwitchElement.style.display === 'none' ) {
252 // handler if viewer is collapsed - to trigger only once drawing after click
253 function onSwitchHandler() {
254 drawPedigreeViewer();
255 onSwitchElement.removeEventListener('click', onSwitchHandler);
260 // var STOCK_ID = "<% $stock_id %>";
261 // var loaded_nodes = {};
262 // d3.json('/ajax/pedigrees/get_full?stock_id='+STOCK_ID,function(nodes){
263 // nodes.forEach(function(node){
264 // loaded_nodes[node.id] = node;
266 // jQuery(document).ready(function(){
270 // function load_nodes(to_load,callback){
271 // if (to_load.length<1) return;
272 // var req_url = "/ajax/pedigrees/get_relationships";
273 // var body = "stock_id="+to_load.join("&stock_id=");
274 // d3.request(req_url)
275 // .mimeType("application/json")
276 // .header("X-Requested-With", "XMLHttpRequest")
277 // .header("Content-Type", "application/x-www-form-urlencoded")
278 // .response(function(xhr){return JSON.parse(xhr.responseText);})
279 // .post(body,function(loaded){
280 // var nodes = loaded.filter(function(node){
281 // if (!loaded_nodes.hasOwnProperty(node.id)){
282 // loaded_nodes[node.id] = node;
291 // function main(start_nodes) {
292 // var tree = d3.pedigreeTree()
294 // .levelMidpoint(50)
299 // .parentsOrdered(true)
300 // .parents(function(node){
301 // return [loaded_nodes[node.parents.mother],loaded_nodes[node.parents.father]].filter(Boolean);
303 // .id(function(node){
306 // .groupChildless(true)
308 // .data(start_nodes)
309 // .excludeFromGrouping([STOCK_ID]);
310 // drawTree(tree(),".pdgtree-canv");
313 // function drawTree(layout,svg_selector,trans){
315 // //set default change-transtion to no duration
316 // trans = trans || d3.transition().duration(0);
318 // //make wrapper(pdg)
319 // var canv = d3.select(svg_selector);
320 // var cbbox = canv.node().getBoundingClientRect();
321 // var canvw = cbbox.width,
322 // canvh = cbbox.height;
323 // var pdg = canv.select('.pedigreeTree');
325 // pdg = canv.append('g').classed('pedigreeTree',true);
329 // var bg = pdg.select('.pdg-bg');
331 // bg = pdg.append('rect')
332 // .classed('pdg-bg',true)
333 // .attr("x",-canvw*500)
334 // .attr("y",-canvh*500)
335 // .attr('width',canvw*1000)
336 // .attr('height',canvh*1000)
337 // .attr('fill',"white")
338 // .attr('stroke','none');
341 // //make scaled content/zoom groups
343 // var pdgtree_width = d3.max([500,layout.x[1]-layout.x[0]]);
344 // var pdgtree_height = d3.max([500,layout.y[1]-layout.y[0]]);
345 // var centeringx = d3.max([0,(500 - (layout.x[1]-layout.x[0]))/2]);
346 // var centeringy = d3.max([0,(500 - (layout.y[1]-layout.y[0]))/2]);
347 // var scale = get_fit_scale(canvw,canvh,pdgtree_width,pdgtree_height,padding);
348 // var offsetx = (canvw-(pdgtree_width)*scale)/2 + centeringx*scale;
349 // var offsety = (canvh-(pdgtree_height)*scale)/2 + centeringy*scale;
351 // var content = pdg.select('.pdg-content');
352 // if (content.empty()){
353 // var zoom = d3.zoom();
354 // var zoom_group = pdg.append('g').classed('pdg-zoom',true).data([zoom]);
356 // content = zoom_group.append('g').classed('pdg-content',true);
357 // content.datum({'zoom':zoom})
358 // zoom.on("zoom",function(){
359 // zoom_group.attr('transform',d3.event.transform);
361 // bg.style("cursor", "all-scroll").call(zoom).call(zoom.transform, d3.zoomIdentity);
362 // bg.on("dblclick.zoom",function(){
363 // zoom.transform(bg.transition(),d3.zoomIdentity);
367 // content.attr('transform',
369 // .translate(offsetx,offsety)
373 // content.datum().zoom.scaleExtent([0.5,d3.max([pdgtree_height,pdgtree_width])/200])
374 // content.transition(trans)
375 // .attr('transform',
377 // .translate(offsetx,offsety)
382 // //set up draw layers
383 // var linkLayer = content.select('.link-layer');
384 // if(linkLayer.empty()){
385 // linkLayer = content.append('g').classed('link-layer',true);
387 // var nodeLayer = content.select('.node-layer');
388 // if(nodeLayer.empty()){
389 // nodeLayer = content.append('g').classed('node-layer',true);
392 // //link curve generators
393 // var stepline = d3.line().curve(d3.curveStepAfter);
394 // var curveline = d3.line().curve(d3.curveBasis);
395 // var build_curve = function(d){
396 // if (d.type=="parent->mid") return curveline(d.path);
397 // if (d.type=="mid->child") return stepline(d.path);
401 // var nodes = nodeLayer.selectAll('.node')
402 // .data(layout.nodes,function(d){return d.id;});
403 // var newNodes = nodes.enter().append('g')
404 // .classed('node',true)
405 // .attr('transform',function(d){
407 // if(d3.event && d3.event.type=="click"){
408 // begin = d3.select(d3.event.target).datum();
410 // return 'translate('+begin.x+','+begin.y+')'
412 // var nodeNodes = newNodes.filter(function(d){
413 // return d.type=="node";
415 // var groupNodes = newNodes.filter(function(d){
416 // return d.type=="node-group";
418 // //draw node group expanders
419 // groupNodes.append("circle")
420 // .style("cursor","pointer")
421 // .attr("fill","purple")
422 // .attr("stroke","purple")
425 // groupNodes.append('text')
426 // .style("cursor","pointer")
428 // .attr("font-size","14px")
429 // .attr("font-weight","bold")
430 // .attr('text-anchor',"middle")
431 // .attr('class', 'glyphicon')
433 // .attr('fill',"white");
434 // //create expander handles on nodes
435 // var expanders = nodeNodes.append('g').classed("expanders",true);
436 // var child_expander = expanders.append("g").classed("child-expander",true)
437 // child_expander.append("path")
438 // .attr("fill","none")
439 // .attr("stroke","purple")
440 // .attr("stroke-width",4)
441 // .attr("d",curveline([[0,20],[0,40]]));
442 // child_expander.append("circle")
443 // .style("cursor","pointer")
444 // .attr("fill","purple")
445 // .attr("stroke","purple")
448 // child_expander.append('text')
449 // .style("cursor","pointer")
452 // .attr("font-size","14px")
453 // .attr("font-weight","bold")
454 // .attr('text-anchor',"middle")
455 // .attr('class', 'glyphicon')
457 // .attr('fill',"white");
458 // child_expander.on("click",function(d){
459 // d3.select(this).on('click',null);
460 // var end_blink = load_blink(d3.select(this).select("circle").node());
461 // var to_load = d.value.children.mother_of.concat(d.value.children.father_of).filter(Boolean).map(String);
462 // load_nodes(to_load,function(nodes){
464 // layout.pdgtree.add(nodes);
465 // drawTree(layout.pdgtree(),".pdgtree-canv",d3.transition().duration(700));
468 // var parent_expander = expanders.append("g").classed("parent-expander",true)
469 // parent_expander.append("path")
470 // .attr("fill","none")
471 // .attr("stroke","purple")
472 // .attr("stroke-width",4)
473 // .attr("d",curveline([[0,0],[0,-40]]));
474 // parent_expander.append("circle")
475 // .style("cursor","pointer")
476 // .attr("fill","purple")
477 // .attr("stroke","purple")
480 // parent_expander.append('text')
481 // .style("cursor","pointer")
484 // .attr("font-size","14px")
485 // .attr("font-weight","bold")
486 // .attr('text-anchor',"middle")
487 // .attr('class', 'glyphicon')
489 // .attr('fill',"white");
490 // parent_expander.on("click",function(d){
491 // d3.select(this).on('click',null);
492 // var end_blink = load_blink(d3.select(this).select("circle").node());
493 // var to_load = [d.value.parents.mother,d.value.parents.father].filter(Boolean).map(String);
494 // load_nodes(to_load,function(nodes){
496 // layout.pdgtree.add(nodes);
497 // drawTree(layout.pdgtree(),".pdgtree-canv",d3.transition().duration(700));
500 // nodeNodes.append('rect').classed("node-name-highlight",true)
501 // .attr('fill',function(d){
502 // return d.id==STOCK_ID?"pink":"none";
504 // .attr('stroke-width',0)
505 // .attr("width",220)
506 // .attr("height",40)
511 // nodeNodes.append('rect').classed("node-name-wrapper",true)
512 // .attr('fill',"white")
513 // .attr('stroke',"grey")
514 // .attr('stroke-width',2)
515 // .attr("width",200)
516 // .attr("height",20)
521 // nodeNodes.filter(function(d){
522 // return d.id!=STOCK_ID;
524 // .append("a").attr("target","_blank")
525 // .attr("href",function(d){
526 // if(d.id==STOCK_ID) return null;
527 // return "/stock/"+d.value.id+"/view";
529 // .append('text').classed('node-name-text',true)
531 // .attr('text-anchor',"middle")
532 // .html(function(d){
533 // return d.value.name;
535 // .attr('fill',"black");
536 // nodeNodes.filter(function(d){
537 // return d.id==STOCK_ID;
539 // .append('text').classed('node-name-text',true)
541 // .attr('text-anchor',"middle")
542 // .html(function(d){
543 // return d.value.name;
545 // .attr('fill',"black");
546 // //set node width to text width
547 // nodeNodes.each(function(d){
548 // var nn = d3.select(this);
549 // var ctl = nn.select('.node-name-text').node().getComputedTextLength();
551 // nn.select('.node-name-wrapper')
554 // nn.select('.node-name-highlight')
555 // .attr("width",w+20)
556 // .attr("x",-(w+20)/2);
558 // var allNodes = newNodes.merge(nodes);
559 // //remove expander handles for nodes without unloaded relatives.
560 // allNodes.each(function(d){
561 // if (d.type=="node"){
562 // var parents_unloaded = [d.value.parents.mother,d.value.parents.father]
563 // .filter(function(node_id){
564 // return !!node_id && !loaded_nodes.hasOwnProperty(node_id);
566 // var children_unloaded = d.value.children.mother_of.concat(d.value.children.father_of)
567 // .filter(function(node_id){
568 // return !!node_id && !loaded_nodes.hasOwnProperty(node_id);
570 // if (parents_unloaded.length<1){
571 // d3.select(this).selectAll(".parent-expander").remove();
573 // if (children_unloaded.length<1){
574 // d3.select(this).selectAll(".child-expander").remove();
578 // allNodes.transition(trans).attr('transform',function(d){
579 // return 'translate('+d.x+','+d.y+')'
581 // allNodes.filter(function(d){return d.type=="node-group"})
582 // .style("cursor", "pointer")
583 // .on("click",function(d){
584 // layout.pdgtree.excludeFromGrouping(d.value.slice(0,10).map(function(d){return d.id;}));
585 // drawTree(layout.pdgtree(),".pdgtree-canv",d3.transition().duration(700).ease(d3.easeLinear));
587 // var oldNodes = nodes.exit().remove();
591 // var link_color = function(d){
592 // if (d.type=="mid->child") return 'purple';
593 // if (d.type=="parent->mid"){
594 // //if its the first parent, red. Otherwise, blue.
595 // var representative = d.sinks[0].type=="node-group"?
596 // d.sinks[0].value[0].value
597 // : d.sinks[0].value;
598 // if (representative.parents.mother == d.source.id){
609 // var links = linkLayer.selectAll('.link')
610 // .data(layout.links,function(d){return d.id;});
611 // var newLinks = links.enter().append('g')
612 // .classed('link',true);
613 // newLinks.append('path')
614 // .attr('d',function(d){
615 // var begin = (d.sink || d.source);
616 // if(d3.event && d3.event.type=="click"){
617 // begin = d3.select(d3.event.target).datum();
619 // return curveline([[begin.x,begin.y],[begin.x,begin.y],[begin.x,begin.y],[begin.x,begin.y]]);
621 // .attr('fill','none')
622 // .attr('stroke',link_color)
623 // .attr('opacity',function(d){
624 // if (d.type=="parent->mid") return 0.7;
627 // .attr('stroke-width',4);
628 // var allLinks = newLinks.merge(links);
629 // allLinks.transition(trans).select('path').attr('d',build_curve);
630 // var oldNodes = links.exit().remove();
633 // function load_blink(node){
635 // var original_fill = d3.select(node).style("fill");
636 // var original_sw = d3.select(node).style("stroke-width");
638 // if (!stop) d3.select(node)
641 // .style("fill", "white")
642 // .style("stroke-width", "5")
645 // .style("fill", original_fill)
646 // .style("stroke-width", original_sw)
647 // .on("end", blink);
650 // return function(){
657 // function get_fit_scale(w1,h1,w2,h2,pad){