adjust dimensions..
[sgn.git] / js / solGS / pca.js
blobeff9e45a016f2cc3ea67d4099df3192e90b7253f
1 /** 
2 * Principal component analysis and scores plotting 
3 * using d3js
4 * Isaak Y Tecle <iyt2@cornell.edu>
6 */
9 JSAN.use("CXGN.List");
10 JSAN.use("jquery.blockUI");
11 JSAN.use('solGS.solGS')
13 jQuery(document).ready( function() {
14     
15     var url = window.location.pathname;
16     
17     if (url.match(/pca\/analysis/) != null) {
18     
19         var list = new CXGN.List();
20         
21         var listMenu = list.listSelect("pca_genotypes", ['accessions', 'trials']);
22        
23         if (listMenu.match(/option/) != null) {
24             
25             jQuery("#pca_genotypes_list").append(listMenu);
27         } else {            
28             jQuery("#pca_genotypes_list").append("<select><option>no lists found - Log in</option></select>");
29         }
30     }
31                
32 });
35 jQuery(document).ready( function() { 
36    
37     var url = window.location.pathname;
39     if (url.match(/solgs\/trait|breeders_toolbox\/trial|breeders\/trial\/|solgs\/selection\//)) {
40        checkPcaResult();  
41     } 
43 });
46 function checkPcaResult () {
47     
48     var popId = solGS.getPopulationDetails();
49    
50     jQuery.ajax({
51         type: 'POST',
52         dataType: 'json',
53         data: {'training_pop_id' : popId.training_pop_id, 'selection_pop_id': popId.selection_pop_id},
54         url: '/pca/check/result/',
55         success: function(response) {
56             if (response.result) {
57                 pcaResult();                                    
58             } else { 
59                 jQuery("#run_pca").show();      
60             }
61         }
62     });
63     
67 jQuery(document).ready( function() { 
69     jQuery("#run_pca").click(function() {
70         pcaResult();
71     }); 
72   
73 });
76 jQuery(document).ready( function() { 
77      
78     var url = window.location.pathname;
79     
80     if (url.match(/pca\/analysis/) != null) {  
81         var listId;
82         
83         jQuery("<option>", {value: '', selected: true}).prependTo("#pca_genotypes_list_select");
84         
85         jQuery("#pca_genotypes_list_select").change(function() {        
86             listId = jQuery(this).find("option:selected").val();              
87                                 
88             if (listId) {                
89                 jQuery("#pca_genotypes_list_upload").click(function() {
90                     loadPcaGenotypesList(listId);
91                 });
92             }
93         }); 
94     }      
95 });
98 function loadPcaGenotypesList(listId) {     
99     
100     var genoList = getPcaGenotypesListData(listId);
101     var listName = genoList.name;
102     var listType = genoList.listType;  
103    
104     if ( listId.length === 0) {       
105         alert('The list is empty. Please select a list with content.' );
106     } else {
107         jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
108         jQuery.blockUI({message: 'Please wait..'});
109                
110         var pcaGenotypes = jQuery("#uploaded_pca_populations_table").doesExist();
111                        
112         if (pcaGenotypes == false) {                              
113             pcaGenotypes = getPcaPopsList(listId);                    
114             jQuery("#uploaded_pca_populations").append(pcaGenotypes).show();                           
115         }
116         else {
117             var addRow = '<tr><td>'
118                 + '<a href="#"  onclick="javascript:setListId(' + listId + ');javascript:pcaResult(); return false;">' 
119                 + listName + '</a>'
120                 + '</td>'
121                 + '<td>' + listType + '</td>'
122                 + '<td id="list_pca_page_' + listId +  '">'
123                 + '<a href="#" onclick="setListId(' + listId + ');pcaResult();return false;">' 
124                 + '[ Run PCA ]' + '</a>'          
125                 + '</td><tr>';
127             var tdId = '#list_pca_page_' + listId;
128             var addedRow = jQuery(tdId).doesExist();
130             if (addedRow == false) {
131                 jQuery("#uploaded_pca_populations_table tr:last").after(addRow);
132             }                          
133         }       
134         jQuery.unblockUI();                                
135     }
140 function pcaResult () {
142     var popId  = solGS.getPopulationDetails();
143     var listId = getListId();
145     if (listId) {
146         popId['training_pop_id'] = 'uploaded_' + listId;
147     }
148    
149     var listName;
150     var listType;
151     
152     if (listId) {
153         var genoList = getPcaGenotypesListData(listId);
154         listName = genoList.name;
155         listType = genoList.listType;
156     }
157     
158     if (listId || popId.training_pop_id || popId.selection_pop_id) {
159         jQuery("#pca_message").html("Running PCA... please wait...");
160         jQuery("#run_pca").hide();
161     }  
162   
163     jQuery.ajax({
164         type: 'POST',
165         dataType: 'json',
166         data: {'training_pop_id': popId.training_pop_id,
167                'selection_pop_id': popId.selection_pop_id,
168                'list_id': listId, 
169                'list_name': listName, 
170                'list_type': listType,
171               },
172         url: '/pca/result',
173         success: function(response) {
174             if (response.status === 'success') {
175         
176                 var scores = response.pca_scores;
177                 var variances = response.pca_variances;
178                 
179                 if (response.pop_id) {
180                     var popId = response.pop_id;
181                 }
182                 
183                 var plotData = { 'scores': scores, 
184                                  'variances': variances, 
185                                  'pop_id': popId, 
186                                  'list_id': listId,
187                                  'list_name': listName
188                                };
189                                         
190                 plotPca(plotData);
191                 jQuery("#pca_message").empty();
192                 jQuery("#run_pca").hide();
194             } else {                
195                 jQuery("#pca_message").html(response.status);
196                 jQuery("#run_pca").show();
197             }
198         },
199         error: function(response) {                    
200             jQuery("#pca_message").html('Error occured running population structure analysis (PCA).');
201             jQuery("#run_pca").show();
202         }  
203     });
204   
208 function getPcaPopsList (listId) {
209    
210     var genoList       = getPcaGenotypesListData(listId);
211     var listName       = genoList.name;
212     var listType       = genoList.listType;
213    
214     var pcaPopsList ='<table id="uploaded_pca_populations_table" style="width:100%; text-align:left"><tr>'
215                                 + '<th>Population</th>'
216                                 + '<th>List type</th>'
217                                 + '<th>Run PCA</th>'
218                                 +'</tr>'
219                                 + '<tr>'
220                                 + '<td>'
221                                 + '<a href="#"  onclick="setListId('+ listId +');pcaResult(); return false;">' 
222                                 + listName + '</a>'
223                                 + '</td>'
224                                 + '<td>' + listType + '</td>'
225                                 + '<td id="list_pca_page_' + listId +  '">'
226                                 + '<a href="#" onclick="setListId(' + listId + ');pcaResult();return false;">' 
227                                 + '[ Run PCA ]'+ '</a>'
228                                 + '</td></tr></table>';
230     return pcaPopsList;
234 jQuery.fn.doesExist = function(){
236         return jQuery(this).length > 0;
238  };
241 function getPcaGenotypesListData(listId) {   
242     
243     var list = new CXGN.List();
244     
245     if (! listId == "") {
246         var listName = list.listNameById(listId);
247         var listType = list.getListType(listId);
248         
249         return {'name'     : listName,
250                 'listType' : listType,
251                };
252     } else {
253         return;
254     }
255    
259 function setListId (listId) {
260      
261     var existingListId = jQuery("#list_id").doesExist();
262    
263     if (existingListId) {
264         jQuery("#list_id").remove();
265     }
266     
267     jQuery("#pca_canvas").append('<input type="hidden" id="list_id" value=' + listId + '></input>');
272 function getListId () {
274     var listId = jQuery("#list_id").val();
275     return listId;  
276       
280 function plotPca(plotData){
282     var scores = plotData.scores;
283     var variances = plotData.variances;
284    
285     var pc12 = [];
286     var pc1  = [];
287     var pc2  = []; 
288     var trials = [];
290     jQuery.each(scores, function(i, pc) {
291         pc12.push( [{'name' : pc[0], 'pc1' : parseFloat(pc[2]), 'pc2': parseFloat(pc[3]), 'trial':pc[1] }]);
292         pc1.push(parseFloat(pc[2]));
293         pc2.push(parseFloat(pc[3]));
294         trials.push(pc[1]);
295     });
296     //console.log(pc12);
297     var height = 300;
298     var width  = 500;
299     var pad    = {left:40, top:20, right:40, bottom:20}; 
300     var totalH = height + pad.top + pad.bottom + 200;
301     var totalW = width + pad.left + pad.right + 200;
302    
303     var svg = d3.select("#pca_canvas")
304         .append("svg")
305         .attr("width", totalW)
306         .attr("height", totalH);
308     var pcaPlot = svg.append("g")
309         .attr("id", "#pca_plot")
310         .attr("transform", "translate(" + (pad.left) + "," + (pad.top) + ")");
311    
312     var pc1Min = d3.min(pc1);
313     var pc1Max = d3.max(pc1); 
314    
315     var pc1Limits = d3.max([Math.abs(d3.min(pc1)), d3.max(pc1)]);
316     var pc2Limits = d3.max([Math.abs(d3.min(pc2)), d3.max(pc2)]);
317   
318     var pc1AxisScale = d3.scale.linear()
319         .domain([0, pc1Limits])
320         .range([0, width/2]);
321     
322     var pc1AxisLabel = d3.scale.linear()
323         .domain([(-1 * pc1Limits), pc1Limits])
324         .range([0, width]);
326     var pc2AxisScale = d3.scale.linear()
327         .domain([0, pc2Limits])
328         .range([0, (height/2)]);
330     var pc1Axis = d3.svg.axis()
331         .scale(pc1AxisLabel)
332         .tickSize(3)
333         .orient("bottom");
334           
335     var pc2AxisLabel = d3.scale.linear()
336         .domain([(-1 * pc2Limits), pc2Limits])
337         .range([height, 0]);
338     
339    var pc2Axis = d3.svg.axis()
340         .scale(pc2AxisLabel)
341         .tickSize(3)
342         .orient("left");
343    
344     var pc1AxisMid = (0.5 * height) + pad.top; 
345     var pc2AxisMid = (0.5 * width) + pad.left;
346   
347     var yMidLineData = [
348         {"x": pc2AxisMid, "y": pad.top}, 
349         {"x": pc2AxisMid, "y": pad.top + height}
350     ];
352     var xMidLineData = [
353         {"x": pad.left, "y": pad.top + height/2}, 
354         {"x": pad.left + width, "y": pad.top + height/2}
355     ];
357     var lineFunction = d3.svg.line()
358         .x(function(d) { return d.x; })
359         .y(function(d) { return d.y; })
360         .interpolate("linear");
362     pcaPlot.append("path")
363         .attr("d", lineFunction(yMidLineData))
364         .attr("stroke", "red")
365         .attr("stroke-width", 1)
366         .attr("fill", "none");
368     pcaPlot.append("path")
369         .attr("d", lineFunction(xMidLineData))
370         .attr("stroke", "green")
371         .attr("stroke-width", 1)
372         .attr("fill", "none");
374     pcaPlot.append("g")
375         .attr("class", "PC1 axis")
376         .attr("transform", "translate(" + pad.left + "," + (pad.top + height) +")")
377         .call(pc1Axis)
378         .selectAll("text")
379         .attr("y", 0)
380         .attr("x", 10)
381         .attr("dy", ".1em")         
382         .attr("transform", "rotate(90)")
383         .attr("fill", "green")
384         .style({"text-anchor":"start", "fill": "#86B404"});
385       
386     pcaPlot.append("g")
387         .attr("class", "PC2 axis")
388         .attr("transform", "translate(" + pad.left +  "," + pad.top  + ")")
389         .call(pc2Axis)
390         .selectAll("text")
391         .attr("y", 0)
392         .attr("x", -10)
393         .attr("fill", "green")
394         .style("fill", "#86B404");
396     pcaPlot.append("g")
397         .attr("id", "pc1_axis_label")
398         .append("text")
399         .text("PC1: " + variances[0][1] + "%" )
400         .attr("y", pad.top + height + 55)
401         .attr("x", width/2)
402         .attr("font-size", 12)
403         .style("fill", "green")
405     pcaPlot.append("g")
406         .attr("id", "pc2_axis_label")
407         .append("text")
408         .text("PC2: " + variances[1][1] + "%" )
409         .attr("transform", "rotate(-90)")
410         .attr("y", -5)
411         .attr("x", -((pad.left + height/2) + 10))
412         .attr("font-size", 12)
413         .style("fill", "red")
415     var grpColor = d3.scale.category10();
417     pcaPlot.append("g")
418         .selectAll("circle")
419         .data(pc12)
420         .enter()
421         .append("circle")
422         .style("fill", function(d) {return grpColor(d[0].trial); })
423         .attr("r", 3)
424         .attr("cx", function(d) { 
425             var xVal = d[0].pc1;            
426             if (xVal >= 0) {
427                 return  (pad.left + (width/2)) + pc1AxisScale(xVal);
428             } else {
429                 return (pad.left + (width/2)) - (-1 * pc1AxisScale(xVal));
430            }
431         })
432         .attr("cy", function(d) {             
433             var yVal = d[0].pc2;
434             
435             if (yVal >= 0) {
436                 return ( pad.top + (height/2)) - pc2AxisScale(yVal);
437             } else {
438                 return (pad.top + (height/2)) +  (-1 * pc2AxisScale(yVal));                  
439             }
440         })        
441         .on("mouseover", function(d) {
442             d3.select(this)
443                 .attr("r", 5)
444                 .style("fill", "#86B404")
445             pcaPlot.append("text")
446                 .attr("id", "dLabel")
447                 .style("fill", "#86B404")              
448                 .text( d[0].name + "(" + d[0].pc1 + "," + d[0].pc2 + ")")
449                 .attr("x", pad.left + 1)
450                 .attr("y", pad.top + 80);
451         })
452         .on("mouseout", function(d) { 
453             d3.select(this)
454                 .attr("r", 3)
455                 .style("fill", function(d) {return grpColor(d[0].trial); })
456             d3.selectAll("text#dLabel").remove();            
457         });
459     pcaPlot.append("rect")
460         .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
461         .attr("height", height)
462         .attr("width", width)
463         .attr("fill", "none")
464         .attr("stroke", "#523CB5")
465         .attr("stroke-width", 1)
466         .attr("pointer-events", "none");
467     
468     var id;   
469     if ( plotData.pop_id) {
470         id = plotData.pop_id;
471     } else {
472         id = plotData.list_id;
473     }
475     var popName = "";
476     if (plotData.list_name) {
477         popName = ' -- ' + plotData.list_name;
478     }
480     var pcaDownload;
481     if (plotData.pop_id)  {
482         pcaDownload = "/download/pca/scores/population/" + id;
483     }
485      pcaPlot.append("a")
486         .attr("xlink:href", pcaDownload)
487         .append("text")
488         .text("[ Download PCA scores ]" + popName)
489         .attr("y", pad.top + height + 75)
490         .attr("x", pad.left)
491         .attr("font-size", 14)
492         .style("fill", "#954A09") 
493     
494     var uniqTrials = jQuery.unique(trials); 
495     var legendValues = [];
496     var cnt = 0;
498     uniqTrials.forEach( function (tr) {
499         legendValues.push([cnt, tr]);
500         cnt++;
501     });
502                         
503     console.log(legendValues);
504     var recLH = 20;
505     var recLW = 20;
507     var legend = pcaPlot.append("g")
508         .attr("class", "cell")
509         .attr("transform", "translate(" + (width + 60) + "," + (height * 0.25) + ")")
510         .attr("height", 100)
511         .attr("width", 100);
513     legend = legend.selectAll("rect")
514         .data(legendValues)  
515         .enter()
516         .append("rect")
517         .attr("x", function (d) { return 1;})
518         .attr("y", function (d) {return 1 + (d[0] * recLH) + (d[0] * 5); })   
519         .attr("width", recLH)
520         .attr("height", recLW)
521         .style("stroke", "black")
522         .attr("fill", function (d) { 
523             return  grpColor(d[1]); 
524         });
526     var legendTxt = pcaPlot.append("g")
527         .attr("transform", "translate(" + (width + 90) + "," + ((height * 0.25) + (0.5 * recLW)) + ")")
528         .attr("id", "legendtext");
530     legendTxt.selectAll("text")
531         .data(legendValues)  
532         .enter()
533         .append("text")              
534         .attr("fill", "#523CB5")
535         .style("fill", "#523CB5")
536         .attr("x", 1)
537         .attr("y", function (d) { return 1 + (d[0] * recLH) + (d[0] * 5); })
538         .text(function (d) { 
539             return d[1]; 
540         })  
541         .attr("dominant-baseline", "middle")
542         .attr("text-anchor", "start");