2 * Principal component analysis and scores plotting
4 * Isaak Y Tecle <iyt2@cornell.edu>
10 JSAN.use("jquery.blockUI");
13 jQuery(document).ready( function() {
15 var url = window.location.pathname;
17 if (url.match(/pca\/analysis/) != null) {
19 var list = new CXGN.List();
21 var listMenu = list.listSelect("pca_genotypes", ['accessions', 'trials']);
23 if (listMenu.match(/option/) != null) {
25 jQuery("#pca_genotypes_list").append(listMenu);
28 jQuery("#pca_genotypes_list").append("<select><option>no lists found - Log in</option></select>");
35 jQuery(document).ready( function() {
37 var url = window.location.pathname;
39 if (url.match(/solgs\/trait|breeders_toolbox\/trial|breeders\/trial\/|solgs\/selection\//)) {
46 function checkPcaResult () {
48 var popId = getPopulationId();
53 url: '/pca/check/result/' + popId,
54 success: function(response) {
55 if (response.result === 'yes') {
58 jQuery("#run_pca").show();
66 jQuery(document).ready( function() {
68 jQuery("#run_pca").click(function() {
75 jQuery(document).ready( function() {
77 var url = window.location.pathname;
79 if (url.match(/pca\/analysis/) != null) {
82 jQuery("<option>", {value: '', selected: true}).prependTo("#pca_genotypes_list_select");
84 jQuery("#pca_genotypes_list_select").change(function() {
85 listId = jQuery(this).find("option:selected").val();
88 jQuery("#pca_genotypes_list_upload").click(function() {
89 loadPcaGenotypesList(listId);
97 function loadPcaGenotypesList(listId) {
99 var genoList = getPcaGenotypesListData(listId);
100 var listName = genoList.name;
101 var listType = genoList.listType;
103 if ( listId.length === 0) {
104 alert('The list is empty. Please select a list with content.' );
106 jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
107 jQuery.blockUI({message: 'Please wait..'});
109 var pcaGenotypes = jQuery("#uploaded_pca_populations_table").doesExist();
111 if (pcaGenotypes == false) {
112 pcaGenotypes = getPcaPopsList(listId);
113 jQuery("#uploaded_pca_populations").append(pcaGenotypes).show();
116 var addRow = '<tr><td>'
117 + '<a href="#" onclick="javascript:setListId(' + listId + ');javascript:pcaResult(); return false;">'
120 + '<td>' + listType + '</td>'
121 + '<td id="list_pca_page_' + listId + '">'
122 + '<a href="#" onclick="setListId(' + listId + ');pcaResult();return false;">'
123 + '[ Run PCA ]' + '</a>'
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);
139 function pcaResult () {
141 var popId = getPopulationId();
142 var listId = getListId();
144 if ( popId && popId.match(/uploaded_/)) {
145 listId = popId.replace("uploaded_", "");
152 var genoList = getPcaGenotypesListData(listId);
153 listName = genoList.name;
154 listType = genoList.listType;
157 if ( popId == null) {
161 if (listId || popId) {
162 jQuery("#pca_message").html("Running PCA... please wait...");
168 data: {'population_id': popId,
170 'list_name': listName,
171 'list_type': listType,
173 url: '/pca/result/' + popId,
174 success: function(response) {
175 if (response.status === 'success') {
177 var scores = response.pca_scores;
178 var variances = response.pca_variances;
180 if (response.pop_id) {
181 popId = response.pop_id;
184 var plotData = { 'scores': scores,
185 'variances': variances,
188 'list_name': listName
192 jQuery("#pca_message").empty();
193 jQuery("#run_pca").hide();
196 jQuery("#pca_message").html(response.status);
199 error: function(response) {
200 jQuery("#pca_message").html('Error occured running population structure analysis (PCA).');
207 function getPcaPopsList (listId) {
209 var genoList = getPcaGenotypesListData(listId);
210 var listName = genoList.name;
211 var listType = genoList.listType;
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>'
220 + '<a href="#" onclick="setListId('+ listId +');pcaResult(); return false;">'
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>';
233 jQuery.fn.doesExist = function(){
235 return jQuery(this).length > 0;
240 function getPcaGenotypesListData(listId) {
242 var list = new CXGN.List();
244 if (! listId == "") {
245 var listName = list.listNameById(listId);
246 var listType = list.getListType(listId);
248 return {'name' : listName,
249 'listType' : listType,
258 function getPopulationId () {
260 var populationId = jQuery("#population_id").val();
263 populationId = jQuery("#model_id").val() || jQuery("#training_pop_id").val();
267 populationId = jQuery("#combo_pops_id").val();
270 var page = window.location.pathname;
272 if (page.match(/solgs\/selection/)) {
274 populationId = jQuery("#selection_pop_id").val();
283 function setListId (listId) {
285 var existingListId = jQuery("#list_id").doesExist();
287 if (existingListId) {
288 jQuery("#list_id").remove();
291 jQuery("#pca_canvas").append("<input type=\"hidden\" id=\"list_id\" value=\"" + listId + "\"></input>");
296 function getListId () {
298 var listId = jQuery("#list_id").val();
304 function plotPca(plotData){
306 var scores = plotData.scores;
307 var variances = plotData.variances;
313 jQuery.each(scores, function(i, pc) {
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]));
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;
327 var svg = d3.select("#pca_canvas")
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) + ")");
336 var pc1Min = d3.min(pc1);
337 var pc1Max = d3.max(pc1);
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)]);
342 var pc1AxisScale = d3.scale.linear()
343 .domain([0, pc1Limits])
344 .range([0, width/2]);
346 var pc1AxisLabel = d3.scale.linear()
347 .domain([(-1 * pc1Limits), pc1Limits])
350 var pc2AxisScale = d3.scale.linear()
351 .domain([0, pc2Limits])
352 .range([0, (height/2)]);
354 var pc1Axis = d3.svg.axis()
359 var pc2AxisLabel = d3.scale.linear()
360 .domain([(-1 * pc2Limits), pc2Limits])
363 var pc2Axis = d3.svg.axis()
368 var pc1AxisMid = 0.5 * (totalH);
369 var pc2AxisMid = 0.5 * (totalW);
372 {"x": pc2AxisMid, "y": pad.top},
373 {"x": pc2AxisMid, "y": pad.top + height}
377 {"x": pad.left, "y": pad.top + height/2},
378 {"x": pad.left + width, "y": pad.top + height/2}
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");
399 .attr("class", "PC1 axis")
400 .attr("transform", "translate(" + pad.left + "," + (pad.top + height) +")")
406 .attr("transform", "rotate(90)")
407 .attr("fill", "green")
408 .style({"text-anchor":"start", "fill": "#86B404"});
411 .attr("class", "PC2 axis")
412 .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
417 .attr("fill", "green")
418 .style("fill", "#86B404");
421 .attr("id", "pc1_axis_label")
423 .text("PC1 -- " + variances[0][1] + "%" )
424 .attr("y", pad.top + height + 30)
426 .attr("font-size", 12)
427 .style("fill", "green")
430 .attr("id", "pc2_axis_label")
432 .text("PC2 -- " + variances[1][1] + "%" )
433 .attr("transform", "rotate(-90)")
435 .attr("x", -((pad.top + height/2) + 10))
436 .attr("font-size", 12)
437 .style("fill", "red")
444 .attr("fill", "#9A2EFE")
446 .attr("cx", function(d) {
449 return (pad.left + (width/2)) + pc1AxisScale(xVal);
451 return (pad.left + (width/2)) - (-1 * pc1AxisScale(xVal));
454 .attr("cy", function(d) {
458 return ( pad.top + (height/2)) - pc2AxisScale(yVal);
460 return (pad.top + (height/2)) + (-1 * pc2AxisScale(yVal));
463 .on("mouseover", function(d) {
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);
474 .on("mouseout", function(d) {
477 .style("fill", "#9A2EFE")
478 d3.selectAll("text#dLabel").remove();
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");
491 if ( plotData.pop_id) {
492 id = plotData.pop_id;
494 id = plotData.list_id;
498 if (plotData.list_name) {
499 popName = ' -- ' + plotData.list_name;
503 if (plotData.pop_id) {
504 pcaDownload = "/download/pca/scores/population/" + id;
508 .attr("xlink:href", pcaDownload)
510 .text("[ Download PCA scores ]" + popName)
511 .attr("y", pad.top + height + 60)
513 .attr("font-size", 14)
514 .style("fill", "#954A09")