ignore emacs backup files also in db/run_all_patches.pl
[sgn.git] / js / solGS / pca.js
blobe9f9babfd487faea2478383307cfcb2bd500ca3c
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() {
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);
27 } else {
28 jQuery("#pca_genotypes_list").append("<select><option>no lists found - Log in</option></select>");
32 });
35 jQuery(document).ready( function() {
37 var url = window.location.pathname;
39 if (url.match(/solgs\/trait|breeders_toolbox\/trial|breeders\/trial\/|solgs\/selection\//)) {
40 checkPcaResult();
43 });
46 function checkPcaResult () {
48 var listId = jQuery('#list_id').val();
50 var popId = solGS.getPopulationDetails();
52 var comboPopsId = jQuery('#combo_pops_id').val();
53 console.log('combo ' + comboPopsId)
54 jQuery.ajax({
55 type: 'POST',
56 dataType: 'json',
57 data: {'list_id': listId,
58 'combo_pops_id' : comboPopsId,
59 'training_pop_id' : popId.training_pop_id,
60 'selection_pop_id': popId.selection_pop_id},
61 url: '/pca/check/result/',
62 success: function(response) {
63 if (response.result) {
65 if (response.list_id) {
66 setListId(response.list_id);
68 console.log('calling pcaResult combo id ' + response.combo_pops_id)
69 console.log('calling pcaResult result ' + response.result)
70 pcaResult();
71 } else {
72 jQuery("#run_pca").show();
76 });
81 jQuery(document).ready( function() {
83 jQuery("#run_pca").click(function() {
84 pcaResult();
85 });
87 });
90 jQuery(document).ready( function() {
92 var url = window.location.pathname;
94 if (url.match(/pca\/analysis/) != null) {
95 var listId;
97 jQuery("<option>", {value: '', selected: true}).prependTo("#pca_genotypes_list_select");
99 jQuery("#pca_genotypes_list_select").change(function() {
100 listId = jQuery(this).find("option:selected").val();
102 if (listId) {
103 jQuery("#pca_genotypes_list_upload").click(function() {
104 loadPcaGenotypesList(listId);
110 checkPcaResult();
115 function loadPcaGenotypesList(listId) {
117 var genoList = getPcaGenotypesListData(listId);
118 var listName = genoList.name;
119 var listType = genoList.listType;
121 if ( listId.length === 0) {
122 alert('The list is empty. Please select a list with content.' );
123 } else {
124 jQuery.blockUI.defaults.applyPlatformOpacityRules = false;
125 jQuery.blockUI({message: 'Please wait..'});
127 var pcaGenotypes = jQuery("#list_pca_populations_table").doesExist();
129 if (pcaGenotypes == false) {
130 pcaGenotypes = getPcaPopsList(listId);
131 jQuery("#list_pca_populations").append(pcaGenotypes).show();
133 else {
134 var addRow = '<tr><td>'
135 + '<a href="#" onclick="javascript:setListId(' + listId + ');javascript:pcaResult(); return false;">'
136 + listName + '</a>'
137 + '</td>'
138 + '<td>' + listType + '</td>'
139 + '<td id="list_pca_page_' + listId + '">'
140 + '<a href="#" onclick="setListId(' + listId + ');pcaResult();return false;">'
141 + '[ Run PCA ]' + '</a>'
142 + '</td><tr>';
144 var tdId = '#list_pca_page_' + listId;
145 var addedRow = jQuery(tdId).doesExist();
147 if (addedRow == false) {
148 jQuery("#list_pca_populations_table tr:last").after(addRow);
151 jQuery.unblockUI();
157 function pcaResult () {
159 var popId = solGS.getPopulationDetails();
160 var listId = getListId();
162 if (listId) {
163 popId['training_pop_id'] = 'list_' + listId;
166 var listName;
167 var listType;
169 if (listId) {
170 var genoList = getPcaGenotypesListData(listId);
171 listName = genoList.name;
172 listType = genoList.listType;
175 if (listId || popId.training_pop_id || popId.selection_pop_id) {
176 jQuery("#pca_message").html("Running PCA... please wait...");
177 jQuery("#run_pca").hide();
180 jQuery.ajax({
181 type: 'POST',
182 dataType: 'json',
183 data: {'training_pop_id': popId.training_pop_id,
184 'selection_pop_id': popId.selection_pop_id,
185 'combo_pops_id': popId.combo_pops_id,
186 'list_id': listId,
187 'list_name': listName,
188 'list_type': listType,
190 url: '/pca/result',
191 success: function(response) {
192 if (response.status === 'success') {
194 if (response.pop_id) {
195 var popId = response.pop_id;
198 var plotData = { 'scores': response.pca_scores,
199 'variances': response.pca_variances,
200 'pop_id': popId,
201 'list_id': listId,
202 'list_name': listName,
203 'trials_names': response.trials_names,
204 'output_link' : response.output_link
207 plotPca(plotData);
208 jQuery("#pca_message").empty();
209 jQuery("#run_pca").hide();
211 } else {
212 jQuery("#pca_message").html(response.status);
213 jQuery("#run_pca").show();
216 error: function(response) {
217 jQuery("#pca_message").html('Error occured running population structure analysis (PCA).');
218 jQuery("#run_pca").show();
225 function getPcaPopsList (listId) {
227 var genoList = getPcaGenotypesListData(listId);
228 var listName = genoList.name;
229 var listType = genoList.listType;
231 var pcaPopsList ='<table id="list_pca_populations_table" style="width:100%; text-align:left"><tr>'
232 + '<th>Population</th>'
233 + '<th>List type</th>'
234 + '<th>Run PCA</th>'
235 +'</tr>'
236 + '<tr>'
237 + '<td>'
238 + '<a href="#" onclick="setListId('+ listId +');pcaResult(); return false;">'
239 + listName + '</a>'
240 + '</td>'
241 + '<td>' + listType + '</td>'
242 + '<td id="list_pca_page_' + listId + '">'
243 + '<a href="#" onclick="setListId(' + listId + ');pcaResult();return false;">'
244 + '[ Run PCA ]'+ '</a>'
245 + '</td></tr></table>';
247 return pcaPopsList;
251 jQuery.fn.doesExist = function(){
253 return jQuery(this).length > 0;
258 function getPcaGenotypesListData(listId) {
260 var list = new CXGN.List();
262 if (! listId == "") {
263 var listName = list.listNameById(listId);
264 var listType = list.getListType(listId);
266 return {'name' : listName,
267 'listType' : listType,
269 } else {
270 return;
276 function setListId (listId) {
278 var existingListId = jQuery("#list_id").doesExist();
279 console.log(listId)
280 //listId = listId.replace('list_', '');
281 console.log(listId)
282 if (existingListId) {
283 jQuery("#list_id").remove();
286 jQuery("#pca_canvas").append('<input type="hidden" id="list_id" value=' + listId + '></input>');
291 function getListId () {
293 var listId = jQuery("#list_id").val();
294 return listId;
299 function plotPca(plotData){
301 var scores = plotData.scores;
302 var variances = plotData.variances;
303 var trialsNames = plotData.trials_names;
305 var pc12 = [];
306 var pc1 = [];
307 var pc2 = [];
308 var trials = [];
310 jQuery.each(scores, function(i, pc) {
311 pc12.push( [{'name' : pc[0], 'pc1' : parseFloat(pc[2]), 'pc2': parseFloat(pc[3]), 'trial':pc[1] }]);
312 pc1.push(parseFloat(pc[2]));
313 pc2.push(parseFloat(pc[3]));
314 trials.push(pc[1]);
317 var height = 300;
318 var width = 500;
319 var pad = {left:40, top:20, right:40, bottom:20};
320 var totalH = height + pad.top + pad.bottom + 200;
321 var totalW = width + pad.left + pad.right + 400;
323 var svg = d3.select("#pca_canvas")
324 .append("svg")
325 .attr("width", totalW)
326 .attr("height", totalH);
328 var pcaPlot = svg.append("g")
329 .attr("id", "#pca_plot")
330 .attr("transform", "translate(" + (pad.left) + "," + (pad.top) + ")");
332 var pc1Min = d3.min(pc1);
333 var pc1Max = d3.max(pc1);
335 var pc1Limits = d3.max([Math.abs(d3.min(pc1)), d3.max(pc1)]);
336 var pc2Limits = d3.max([Math.abs(d3.min(pc2)), d3.max(pc2)]);
338 var pc1AxisScale = d3.scale.linear()
339 .domain([0, pc1Limits])
340 .range([0, width/2]);
342 var pc1AxisLabel = d3.scale.linear()
343 .domain([(-1 * pc1Limits), pc1Limits])
344 .range([0, width]);
346 var pc2AxisScale = d3.scale.linear()
347 .domain([0, pc2Limits])
348 .range([0, (height/2)]);
350 var pc1Axis = d3.svg.axis()
351 .scale(pc1AxisLabel)
352 .tickSize(3)
353 .orient("bottom");
355 var pc2AxisLabel = d3.scale.linear()
356 .domain([(-1 * pc2Limits), pc2Limits])
357 .range([height, 0]);
359 var pc2Axis = d3.svg.axis()
360 .scale(pc2AxisLabel)
361 .tickSize(3)
362 .orient("left");
364 var pc1AxisMid = (0.5 * height) + pad.top;
365 var pc2AxisMid = (0.5 * width) + pad.left;
367 var yMidLineData = [
368 {"x": pc2AxisMid, "y": pad.top},
369 {"x": pc2AxisMid, "y": pad.top + height}
372 var xMidLineData = [
373 {"x": pad.left, "y": pad.top + height/2},
374 {"x": pad.left + width, "y": pad.top + height/2}
377 var lineFunction = d3.svg.line()
378 .x(function(d) { return d.x; })
379 .y(function(d) { return d.y; })
380 .interpolate("linear");
382 pcaPlot.append("path")
383 .attr("d", lineFunction(yMidLineData))
384 .attr("stroke", "red")
385 .attr("stroke-width", 1)
386 .attr("fill", "none");
388 pcaPlot.append("path")
389 .attr("d", lineFunction(xMidLineData))
390 .attr("stroke", "green")
391 .attr("stroke-width", 1)
392 .attr("fill", "none");
394 pcaPlot.append("g")
395 .attr("class", "PC1 axis")
396 .attr("transform", "translate(" + pad.left + "," + (pad.top + height) +")")
397 .call(pc1Axis)
398 .selectAll("text")
399 .attr("y", 0)
400 .attr("x", 10)
401 .attr("dy", ".1em")
402 .attr("transform", "rotate(90)")
403 .attr("fill", "green")
404 .style({"text-anchor":"start", "fill": "#86B404"});
406 pcaPlot.append("g")
407 .attr("class", "PC2 axis")
408 .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
409 .call(pc2Axis)
410 .selectAll("text")
411 .attr("y", 0)
412 .attr("x", -10)
413 .attr("fill", "green")
414 .style("fill", "#86B404");
416 pcaPlot.append("g")
417 .attr("id", "pc1_axis_label")
418 .append("text")
419 .text("PC1: " + variances[0][1] + "%" )
420 .attr("y", pad.top + height + 55)
421 .attr("x", width/2)
422 .attr("font-size", 12)
423 .style("fill", "green")
425 pcaPlot.append("g")
426 .attr("id", "pc2_axis_label")
427 .append("text")
428 .text("PC2: " + variances[1][1] + "%" )
429 .attr("transform", "rotate(-90)")
430 .attr("y", -5)
431 .attr("x", -((pad.left + height/2) + 10))
432 .attr("font-size", 12)
433 .style("fill", "red")
435 var grpColor = d3.scale.category10();
437 pcaPlot.append("g")
438 .selectAll("circle")
439 .data(pc12)
440 .enter()
441 .append("circle")
442 .style("fill", function(d) {return grpColor(d[0].trial); })
443 .attr("r", 3)
444 .attr("cx", function(d) {
445 var xVal = d[0].pc1;
446 if (xVal >= 0) {
447 return (pad.left + (width/2)) + pc1AxisScale(xVal);
448 } else {
449 return (pad.left + (width/2)) - (-1 * pc1AxisScale(xVal));
452 .attr("cy", function(d) {
453 var yVal = d[0].pc2;
455 if (yVal >= 0) {
456 return ( pad.top + (height/2)) - pc2AxisScale(yVal);
457 } else {
458 return (pad.top + (height/2)) + (-1 * pc2AxisScale(yVal));
461 .on("mouseover", function(d) {
462 d3.select(this)
463 .attr("r", 5)
464 .style("fill", "#86B404")
465 pcaPlot.append("text")
466 .attr("id", "dLabel")
467 .style("fill", "#86B404")
468 .text( d[0].name + "(" + d[0].pc1 + "," + d[0].pc2 + ")")
469 .attr("x", pad.left + 1)
470 .attr("y", pad.top + 80);
472 .on("mouseout", function(d) {
473 d3.select(this)
474 .attr("r", 3)
475 .style("fill", function(d) {return grpColor(d[0].trial); })
476 d3.selectAll("text#dLabel").remove();
479 pcaPlot.append("rect")
480 .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
481 .attr("height", height)
482 .attr("width", width)
483 .attr("fill", "none")
484 .attr("stroke", "#523CB5")
485 .attr("stroke-width", 1)
486 .attr("pointer-events", "none");
488 var id;
489 if ( plotData.pop_id) {
490 id = plotData.pop_id;
491 } else {
492 id = plotData.list_id;
495 var popName = "";
496 if (plotData.list_name) {
497 popName = ' -- ' + plotData.list_name;
500 var pcaDownload;
501 if (plotData.pop_id) {
502 pcaDownload = "/download/pca/scores/population/" + id;
505 pcaPlot.append("a")
506 .attr("xlink:href", pcaDownload)
507 .append("text")
508 .text("[ Download PCA scores ]" + popName)
509 .attr("y", pad.top + height + 75)
510 .attr("x", pad.left)
511 .attr("font-size", 14)
512 .style("fill", "#954A09")
515 var shareLink;
516 if (plotData.output_link) {
517 shareLink = plotData.output_link;
520 pcaPlot.append("a")
521 .attr("xlink:href", shareLink)
522 .append("text")
523 .text("[Share plot ]")
524 .attr("y", pad.top + height + 100)
525 .attr("x", pad.left)
526 .attr("font-size", 14)
527 .style("fill", "#954A09")
529 if (trialsNames && Object.keys(trialsNames).length > 1) {
530 var trialsIds = jQuery.unique(trials);
531 trialsIds = jQuery.unique(trialsIds);
533 var legendValues = [];
534 var cnt = 0;
536 var allTrialsNames = [];
538 for (var tr in trialsNames) {
539 allTrialsNames.push(trialsNames[tr]);
542 trialsIds.forEach( function (id) {
543 var trialName = trialsNames[id];
544 if (isNaN(id)) {trialName = allTrialsNames.join(' & ');}
545 legendValues.push([cnt, id, trialName]);
546 cnt++;
549 var recLH = 20;
550 var recLW = 20;
552 var legend = pcaPlot.append("g")
553 .attr("class", "cell")
554 .attr("transform", "translate(" + (width + 60) + "," + (height * 0.25) + ")")
555 .attr("height", 100)
556 .attr("width", 100);
558 legend = legend.selectAll("rect")
559 .data(legendValues)
560 .enter()
561 .append("rect")
562 .attr("x", function (d) { return 1;})
563 .attr("y", function (d) {return 1 + (d[0] * recLH) + (d[0] * 5); })
564 .attr("width", recLH)
565 .attr("height", recLW)
566 .style("stroke", "black")
567 .attr("fill", function (d) {
568 return grpColor(d[1]);
571 var legendTxt = pcaPlot.append("g")
572 .attr("transform", "translate(" + (width + 90) + "," + ((height * 0.25) + (0.5 * recLW)) + ")")
573 .attr("id", "legendtext");
575 legendTxt.selectAll("text")
576 .data(legendValues)
577 .enter()
578 .append("text")
579 .attr("fill", "#523CB5")
580 .style("fill", "#523CB5")
581 .attr("x", 1)
582 .attr("y", function (d) { return 1 + (d[0] * recLH) + (d[0] * 5); })
583 .text(function (d) {
584 return d[2];
586 .attr("dominant-baseline", "middle")
587 .attr("text-anchor", "start");