minor fixes
[sgn.git] / js / solGS / pca.js
blob2891a42386dccbe5b1bf1b40daff94368864134f
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");
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 = getPopulationId();
50     jQuery.ajax({
51         type: 'POST',
52         dataType: 'json',
53         url: '/pca/check/result/' + popId,
54         success: function(response) {
55             if (response.result === 'yes') {
56                 pcaResult();                                    
57             } else { 
58                 jQuery("#run_pca").show();      
59             }
60         }
61     });
62     
66 jQuery(document).ready( function() { 
68     jQuery("#run_pca").click(function() {
69         pcaResult();
70     }); 
71   
72 });
75 jQuery(document).ready( function() { 
76      
77     var url = window.location.pathname;
78     
79     if (url.match(/pca\/analysis/) != null) {  
80         var listId;
81         
82         jQuery("<option>", {value: '', selected: true}).prependTo("#pca_genotypes_list_select");
83         
84         jQuery("#pca_genotypes_list_select").change(function() {        
85             listId = jQuery(this).find("option:selected").val();              
86                                 
87             if (listId) {                
88                 jQuery("#pca_genotypes_list_upload").click(function() {
89                     loadPcaGenotypesList(listId);
90                 });
91             }
92         }); 
93     }      
94 });
97 function loadPcaGenotypesList(listId) {     
98     
99     var genoList = getPcaGenotypesListData(listId);
100     var listName = genoList.name;
101     var listType = genoList.listType;  
102    
103     if ( listId.length === 0) {       
104         alert('The list is empty. Please select a list with content.' );
105     } else {
106         jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
107         jQuery.blockUI({message: 'Please wait..'});
108                
109         var pcaGenotypes = jQuery("#uploaded_pca_populations_table").doesExist();
110                        
111         if (pcaGenotypes == false) {                              
112             pcaGenotypes = getPcaPopsList(listId);                    
113             jQuery("#uploaded_pca_populations").append(pcaGenotypes).show();                           
114         }
115         else {
116             var addRow = '<tr><td>'
117                 + '<a href="#"  onclick="javascript:setListId(' + listId + ');javascript:pcaResult(); return false;">' 
118                 + listName + '</a>'
119                 + '</td>'
120                 + '<td>' + listType + '</td>'
121                 + '<td id="list_pca_page_' + listId +  '">'
122                 + '<a href="#" onclick="setListId(' + listId + ');pcaResult();return false;">' 
123                 + '[ Run PCA ]' + '</a>'          
124                 + '</td><tr>';
126             var tdId = '#list_pca_page_' + listId;
127             var addedRow = jQuery(tdId).doesExist();
129             if (addedRow == false) {
130                 jQuery("#uploaded_pca_populations_table tr:last").after(addRow);
131             }                          
132         }       
133         jQuery.unblockUI();                                
134     }
139 function pcaResult () {
141     var popId  = getPopulationId();
142     var listId = getListId();
144     if ( popId && popId.match(/uploaded_/)) {   
145         listId = popId.replace("uploaded_", "");
146     }
148     var listName;
149     var listType;
150     
151     if (listId) {
152     var genoList = getPcaGenotypesListData(listId);
153         listName = genoList.name;
154         listType = genoList.listType;
155     }
156     
157     if ( popId == null) {
158         popId = listId;
159     }
161     if (listId || popId) {
162         jQuery("#pca_message").html("Running PCA... please wait...");
163     }  
164    
165     jQuery.ajax({
166         type: 'POST',
167         dataType: 'json',
168         data: {'population_id': popId, 
169                'list_id': listId, 
170                'list_name': listName, 
171                'list_type': listType,
172               },
173         url: '/pca/result/' + popId,
174         success: function(response) {
175             if (response.status === 'success') {
176         
177                 var scores = response.pca_scores;
178                 var variances = response.pca_variances;
179                 
180                 if (response.pop_id) {
181                     popId = response.pop_id;
182                 }
183                 
184                 var plotData = { 'scores': scores, 
185                                  'variances': variances, 
186                                  'pop_id': popId, 
187                                  'list_id': listId,
188                                  'list_name': listName
189                                };
190                                         
191                 plotPca(plotData);
192                 jQuery("#pca_message").empty();
193                 jQuery("#run_pca").hide();
195             } else {                
196                 jQuery("#pca_message").html(response.status); 
197             }
198         },
199         error: function(response) {                    
200             jQuery("#pca_message").html('Error occured running population structure analysis (PCA).');
201         }  
202     });
203   
207 function getPcaPopsList (listId) {
208    
209     var genoList       = getPcaGenotypesListData(listId);
210     var listName       = genoList.name;
211     var listType       = genoList.listType;
212    
213     var pcaPopsList ='<table id="uploaded_pca_populations_table" style="width:100%; text-align:left"><tr>'
214                                 + '<th>Population</th>'
215                                 + '<th>List type</th>'
216                                 + '<th>Run PCA</th>'
217                                 +'</tr>'
218                                 + '<tr>'
219                                 + '<td>'
220                                 + '<a href="#"  onclick="setListId('+ listId +');pcaResult(); return false;">' 
221                                 + listName + '</a>'
222                                 + '</td>'
223                                 + '<td>' + listType + '</td>'
224                                 + '<td id="list_pca_page_' + listId +  '">'
225                                 + '<a href="#" onclick="setListId(' + listId + ');pcaResult();return false;">' 
226                                 + '[ Run PCA ]'+ '</a>'
227                                 + '</td></tr></table>';
229     return pcaPopsList;
233 jQuery.fn.doesExist = function(){
235         return jQuery(this).length > 0;
237  };
240 function getPcaGenotypesListData(listId) {   
241     
242     var list = new CXGN.List();
243     
244     if (! listId == "") {
245         var listName = list.listNameById(listId);
246         var listType = list.getListType(listId);
247         
248         return {'name'     : listName,
249                 'listType' : listType,
250                };
251     } else {
252         return;
253     }
254    
258 function getPopulationId () {
260     var populationId = jQuery("#population_id").val();
261     
262     if (!populationId) {       
263         populationId = jQuery("#model_id").val() || jQuery("#training_pop_id").val();
264     }
266     if (!populationId) {
267         populationId = jQuery("#combo_pops_id").val();
268     }
270     var page = window.location.pathname;
272     if (page.match(/solgs\/selection/)) {
273         
274         populationId = jQuery("#selection_pop_id").val();
275     } 
276   
277     
278     return populationId;
279         
283 function setListId (listId) {
284      
285     var existingListId = jQuery("#list_id").doesExist();
286     
287     if (existingListId) {
288         jQuery("#list_id").remove();
289     }
290     
291     jQuery("#pca_canvas").append("<input type=\"hidden\" id=\"list_id\" value=\"" + listId + "\"></input>");
296 function getListId () {
298     var listId = jQuery("#list_id").val();
299     return listId;  
300       
304 function plotPca(plotData){
306     var scores = plotData.scores;
307     var variances = plotData.variances;
308    
309     var pc12 = [];
310     var pc1  = [];
311     var pc2  = []; 
313     jQuery.each(scores, function(i, pc) {
314                    
315         pc12.push( [{'name' : pc[0], 'pc1' : parseFloat(pc[1]), 'pc2': parseFloat(pc[2])}] );
316         pc1.push(parseFloat(pc[1]));
317         pc2.push(parseFloat(pc[2]));
319     });
320      
321     var height = 300;
322     var width  = 500;
323     var pad    = {left:20, top:20, right:20, bottom: 100}; 
324     var totalH = height + pad.top + pad.bottom;
325     var totalW = width + pad.left + pad.right;
326    
327     var svg = d3.select("#pca_canvas")
328         .append("svg")
329         .attr("width", totalW)
330         .attr("height", totalH);
332     var pcaPlot = svg.append("g")
333         .attr("id", "#pca_plot")
334         .attr("transform", "translate(" + (pad.left) + "," + (pad.top) + ")");
335    
336     var pc1Min = d3.min(pc1);
337     var pc1Max = d3.max(pc1); 
338    
339     var pc1Limits = d3.max([Math.abs(d3.min(pc1)), d3.max(pc1)]);
340     var pc2Limits = d3.max([Math.abs(d3.min(pc2)), d3.max(pc2)]);
341   
342     var pc1AxisScale = d3.scale.linear()
343         .domain([0, pc1Limits])
344         .range([0, width/2]);
345     
346     var pc1AxisLabel = d3.scale.linear()
347         .domain([(-1 * pc1Limits), pc1Limits])
348         .range([0, width]);
350     var pc2AxisScale = d3.scale.linear()
351         .domain([0, pc2Limits])
352         .range([0, (height/2)]);
354     var pc1Axis = d3.svg.axis()
355         .scale(pc1AxisLabel)
356         .tickSize(3)
357         .orient("bottom");
358           
359     var pc2AxisLabel = d3.scale.linear()
360         .domain([(-1 * pc2Limits), pc2Limits])
361         .range([height, 0]);
362     
363    var pc2Axis = d3.svg.axis()
364         .scale(pc2AxisLabel)
365         .tickSize(3)
366         .orient("left");
367    
368     var pc1AxisMid = 0.5 * (totalH); 
369     var pc2AxisMid = 0.5 * (totalW);
370   
371     var yMidLineData = [
372         {"x": pc2AxisMid, "y": pad.top}, 
373         {"x": pc2AxisMid, "y": pad.top + height}
374     ];
376     var xMidLineData = [
377         {"x": pad.left, "y": pad.top + height/2}, 
378         {"x": pad.left + width, "y": pad.top + height/2}
379     ];
381     var lineFunction = d3.svg.line()
382         .x(function(d) { return d.x; })
383         .y(function(d) { return d.y; })
384         .interpolate("linear");
386     pcaPlot.append("path")
387         .attr("d", lineFunction(yMidLineData))
388         .attr("stroke", "red")
389         .attr("stroke-width", 1)
390         .attr("fill", "none");
392     pcaPlot.append("path")
393         .attr("d", lineFunction(xMidLineData))
394         .attr("stroke", "green")
395         .attr("stroke-width", 1)
396         .attr("fill", "none");
398     pcaPlot.append("g")
399         .attr("class", "PC1 axis")
400         .attr("transform", "translate(" + pad.left + "," + (pad.top + height) +")")
401         .call(pc1Axis)
402         .selectAll("text")
403         .attr("y", 0)
404         .attr("x", 10)
405         .attr("dy", ".1em")         
406         .attr("transform", "rotate(90)")
407         .attr("fill", "green")
408         .style({"text-anchor":"start", "fill": "#86B404"});
409       
410     pcaPlot.append("g")
411         .attr("class", "PC2 axis")
412         .attr("transform", "translate(" + pad.left +  "," + pad.top  + ")")
413         .call(pc2Axis)
414         .selectAll("text")
415         .attr("y", 0)
416         .attr("x", -10)
417         .attr("fill", "green")
418         .style("fill", "#86B404");
420     pcaPlot.append("g")
421         .attr("id", "pc1_axis_label")
422         .append("text")
423         .text("PC1 -- " + variances[0][1] + "%" )
424         .attr("y", pad.top + height + 30)
425         .attr("x", width/2)
426         .attr("font-size", 12)
427         .style("fill", "green")
429     pcaPlot.append("g")
430         .attr("id", "pc2_axis_label")
431         .append("text")
432         .text("PC2 -- " + variances[1][1] + "%" )
433         .attr("transform", "rotate(-90)")
434         .attr("y", -5)
435         .attr("x", -((pad.top + height/2) + 10))
436         .attr("font-size", 12)
437         .style("fill", "red")
439     pcaPlot.append("g")
440         .selectAll("circle")
441         .data(pc12)
442         .enter()
443         .append("circle")
444         .attr("fill", "#9A2EFE")
445         .attr("r", 3)
446         .attr("cx", function(d) { 
447             var xVal = d[0].pc1;            
448             if (xVal >= 0) {
449                 return  (pad.left + (width/2)) + pc1AxisScale(xVal);
450             } else {
451                 return (pad.left + (width/2)) - (-1 * pc1AxisScale(xVal));
452            }
453         })
454         .attr("cy", function(d) {             
455             var yVal = d[0].pc2;
456             
457             if (yVal >= 0) {
458                 return ( pad.top + (height/2)) - pc2AxisScale(yVal);
459             } else {
460                 return (pad.top + (height/2)) +  (-1 * pc2AxisScale(yVal));                  
461             }
462         })        
463         .on("mouseover", function(d) {
464             d3.select(this)
465                 .attr("r", 5)
466                 .style("fill", "#86B404")
467             pcaPlot.append("text")
468                 .attr("id", "dLabel")
469                 .style("fill", "#86B404")              
470                 .text( d[0].name + "(" + d[0].pc1 + "," + d[0].pc2 + ")")
471                 .attr("x", pad.left + 1)
472                 .attr("y", pad.top + 80);
473         })
474         .on("mouseout", function(d) { 
475             d3.select(this)
476                 .attr("r", 3)
477                 .style("fill", "#9A2EFE")
478             d3.selectAll("text#dLabel").remove();            
479         });
481     pcaPlot.append("rect")
482         .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
483         .attr("height", height)
484         .attr("width", width)
485         .attr("fill", "none")
486         .attr("stroke", "#523CB5")
487         .attr("stroke-width", 1)
488         .attr("pointer-events", "none");
489     
490     var id;   
491     if ( plotData.pop_id) {
492         id = plotData.pop_id;
493     } else {
494         id = plotData.list_id;
495     }
497     var popName = "";
498     if (plotData.list_name) {
499         popName = ' -- ' + plotData.list_name;
500     }
502     var pcaDownload;
503     if (plotData.pop_id)  {
504         pcaDownload = "/download/pca/scores/population/" + id;
505     }
507      pcaPlot.append("a")
508         .attr("xlink:href", pcaDownload)
509         .append("text")
510         .text("[ Download PCA scores ]" + popName)
511         .attr("y", pad.top + height + 60)
512         .attr("x", pad.left)
513         .attr("font-size", 14)
514         .style("fill", "#954A09") 
515