2 * Principal component analysis and scores plotting
4 * Isaak Y Tecle <iyt2@cornell.edu>
10 JSAN.use("jquery.blockUI");
11 JSAN.use('solGS.solGS')
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 = solGS.getPopulationDetails();
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) {
59 jQuery("#run_pca").show();
67 jQuery(document).ready( function() {
69 jQuery("#run_pca").click(function() {
76 jQuery(document).ready( function() {
78 var url = window.location.pathname;
80 if (url.match(/pca\/analysis/) != null) {
83 jQuery("<option>", {value: '', selected: true}).prependTo("#pca_genotypes_list_select");
85 jQuery("#pca_genotypes_list_select").change(function() {
86 listId = jQuery(this).find("option:selected").val();
89 jQuery("#pca_genotypes_list_upload").click(function() {
90 loadPcaGenotypesList(listId);
98 function loadPcaGenotypesList(listId) {
100 var genoList = getPcaGenotypesListData(listId);
101 var listName = genoList.name;
102 var listType = genoList.listType;
104 if ( listId.length === 0) {
105 alert('The list is empty. Please select a list with content.' );
107 jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
108 jQuery.blockUI({message: 'Please wait..'});
110 var pcaGenotypes = jQuery("#uploaded_pca_populations_table").doesExist();
112 if (pcaGenotypes == false) {
113 pcaGenotypes = getPcaPopsList(listId);
114 jQuery("#uploaded_pca_populations").append(pcaGenotypes).show();
117 var addRow = '<tr><td>'
118 + '<a href="#" onclick="javascript:setListId(' + listId + ');javascript:pcaResult(); return false;">'
121 + '<td>' + listType + '</td>'
122 + '<td id="list_pca_page_' + listId + '">'
123 + '<a href="#" onclick="setListId(' + listId + ');pcaResult();return false;">'
124 + '[ Run PCA ]' + '</a>'
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);
140 function pcaResult () {
142 var popId = solGS.getPopulationDetails();
143 var listId = getListId();
146 popId['training_pop_id'] = 'uploaded_' + listId;
153 var genoList = getPcaGenotypesListData(listId);
154 listName = genoList.name;
155 listType = genoList.listType;
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();
166 data: {'training_pop_id': popId.training_pop_id,
167 'selection_pop_id': popId.selection_pop_id,
169 'list_name': listName,
170 'list_type': listType,
173 success: function(response) {
174 if (response.status === 'success') {
176 var scores = response.pca_scores;
177 var variances = response.pca_variances;
179 if (response.pop_id) {
180 var popId = response.pop_id;
183 var plotData = { 'scores': scores,
184 'variances': variances,
187 'list_name': listName
191 jQuery("#pca_message").empty();
192 jQuery("#run_pca").hide();
195 jQuery("#pca_message").html(response.status);
196 jQuery("#run_pca").show();
199 error: function(response) {
200 jQuery("#pca_message").html('Error occured running population structure analysis (PCA).');
201 jQuery("#run_pca").show();
208 function getPcaPopsList (listId) {
210 var genoList = getPcaGenotypesListData(listId);
211 var listName = genoList.name;
212 var listType = genoList.listType;
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>'
221 + '<a href="#" onclick="setListId('+ listId +');pcaResult(); return false;">'
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>';
234 jQuery.fn.doesExist = function(){
236 return jQuery(this).length > 0;
241 function getPcaGenotypesListData(listId) {
243 var list = new CXGN.List();
245 if (! listId == "") {
246 var listName = list.listNameById(listId);
247 var listType = list.getListType(listId);
249 return {'name' : listName,
250 'listType' : listType,
259 function setListId (listId) {
261 var existingListId = jQuery("#list_id").doesExist();
263 if (existingListId) {
264 jQuery("#list_id").remove();
267 jQuery("#pca_canvas").append('<input type="hidden" id="list_id" value=' + listId + '></input>');
272 function getListId () {
274 var listId = jQuery("#list_id").val();
280 function plotPca(plotData){
282 var scores = plotData.scores;
283 var variances = plotData.variances;
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]));
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;
303 var svg = d3.select("#pca_canvas")
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) + ")");
312 var pc1Min = d3.min(pc1);
313 var pc1Max = d3.max(pc1);
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)]);
318 var pc1AxisScale = d3.scale.linear()
319 .domain([0, pc1Limits])
320 .range([0, width/2]);
322 var pc1AxisLabel = d3.scale.linear()
323 .domain([(-1 * pc1Limits), pc1Limits])
326 var pc2AxisScale = d3.scale.linear()
327 .domain([0, pc2Limits])
328 .range([0, (height/2)]);
330 var pc1Axis = d3.svg.axis()
335 var pc2AxisLabel = d3.scale.linear()
336 .domain([(-1 * pc2Limits), pc2Limits])
339 var pc2Axis = d3.svg.axis()
344 var pc1AxisMid = (0.5 * height) + pad.top;
345 var pc2AxisMid = (0.5 * width) + pad.left;
348 {"x": pc2AxisMid, "y": pad.top},
349 {"x": pc2AxisMid, "y": pad.top + height}
353 {"x": pad.left, "y": pad.top + height/2},
354 {"x": pad.left + width, "y": pad.top + height/2}
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");
375 .attr("class", "PC1 axis")
376 .attr("transform", "translate(" + pad.left + "," + (pad.top + height) +")")
382 .attr("transform", "rotate(90)")
383 .attr("fill", "green")
384 .style({"text-anchor":"start", "fill": "#86B404"});
387 .attr("class", "PC2 axis")
388 .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
393 .attr("fill", "green")
394 .style("fill", "#86B404");
397 .attr("id", "pc1_axis_label")
399 .text("PC1: " + variances[0][1] + "%" )
400 .attr("y", pad.top + height + 55)
402 .attr("font-size", 12)
403 .style("fill", "green")
406 .attr("id", "pc2_axis_label")
408 .text("PC2: " + variances[1][1] + "%" )
409 .attr("transform", "rotate(-90)")
411 .attr("x", -((pad.left + height/2) + 10))
412 .attr("font-size", 12)
413 .style("fill", "red")
415 var grpColor = d3.scale.category10();
422 .style("fill", function(d) {return grpColor(d[0].trial); })
424 .attr("cx", function(d) {
427 return (pad.left + (width/2)) + pc1AxisScale(xVal);
429 return (pad.left + (width/2)) - (-1 * pc1AxisScale(xVal));
432 .attr("cy", function(d) {
436 return ( pad.top + (height/2)) - pc2AxisScale(yVal);
438 return (pad.top + (height/2)) + (-1 * pc2AxisScale(yVal));
441 .on("mouseover", function(d) {
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);
452 .on("mouseout", function(d) {
455 .style("fill", function(d) {return grpColor(d[0].trial); })
456 d3.selectAll("text#dLabel").remove();
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");
469 if ( plotData.pop_id) {
470 id = plotData.pop_id;
472 id = plotData.list_id;
476 if (plotData.list_name) {
477 popName = ' -- ' + plotData.list_name;
481 if (plotData.pop_id) {
482 pcaDownload = "/download/pca/scores/population/" + id;
486 .attr("xlink:href", pcaDownload)
488 .text("[ Download PCA scores ]" + popName)
489 .attr("y", pad.top + height + 75)
491 .attr("font-size", 14)
492 .style("fill", "#954A09")
494 var uniqTrials = jQuery.unique(trials);
495 var legendValues = [];
498 uniqTrials.forEach( function (tr) {
499 legendValues.push([cnt, tr]);
503 console.log(legendValues);
507 var legend = pcaPlot.append("g")
508 .attr("class", "cell")
509 .attr("transform", "translate(" + (width + 60) + "," + (height * 0.25) + ")")
513 legend = legend.selectAll("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]);
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")
534 .attr("fill", "#523CB5")
535 .style("fill", "#523CB5")
537 .attr("y", function (d) { return 1 + (d[0] * recLH) + (d[0] * 5); })
541 .attr("dominant-baseline", "middle")
542 .attr("text-anchor", "start");