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 listId
= jQuery('#list_id').val();
50 var popId
= solGS
.getPopulationDetails();
52 var comboPopsId
= jQuery('#combo_pops_id').val();
53 console
.log('combo ' + comboPopsId
)
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
)
72 jQuery("#run_pca").show();
81 jQuery(document
).ready( function() {
83 jQuery("#run_pca").click(function() {
90 jQuery(document
).ready( function() {
92 var url
= window
.location
.pathname
;
94 if (url
.match(/pca\/analysis/) != null) {
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();
103 jQuery("#pca_genotypes_list_upload").click(function() {
104 loadPcaGenotypesList(listId
);
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.' );
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();
134 var addRow
= '<tr><td>'
135 + '<a href="#" onclick="javascript:setListId(' + listId
+ ');javascript:pcaResult(); return false;">'
138 + '<td>' + listType
+ '</td>'
139 + '<td id="list_pca_page_' + listId
+ '">'
140 + '<a href="#" onclick="setListId(' + listId
+ ');pcaResult();return false;">'
141 + '[ Run PCA ]' + '</a>'
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
);
157 function pcaResult () {
159 var popId
= solGS
.getPopulationDetails();
160 var listId
= getListId();
163 popId
['training_pop_id'] = 'list_' + 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();
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
,
187 'list_name': listName
,
188 'list_type': listType
,
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
,
202 'list_name': listName
,
203 'trials_names': response
.trials_names
,
204 'output_link' : response
.output_link
208 jQuery("#pca_message").empty();
209 jQuery("#run_pca").hide();
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>'
238 + '<a href="#" onclick="setListId('+ listId
+');pcaResult(); return false;">'
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>';
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
,
276 function setListId (listId
) {
278 var existingListId
= jQuery("#list_id").doesExist();
280 //listId = listId.replace('list_', '');
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();
299 function plotPca(plotData
){
301 var scores
= plotData
.scores
;
302 var variances
= plotData
.variances
;
303 var trialsNames
= plotData
.trials_names
;
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]));
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")
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
])
346 var pc2AxisScale
= d3
.scale
.linear()
347 .domain([0, pc2Limits
])
348 .range([0, (height
/2)]);
350 var pc1Axis
= d3
.svg
.axis()
355 var pc2AxisLabel
= d3
.scale
.linear()
356 .domain([(-1 * pc2Limits
), pc2Limits
])
359 var pc2Axis
= d3
.svg
.axis()
364 var pc1AxisMid
= (0.5 * height
) + pad
.top
;
365 var pc2AxisMid
= (0.5 * width
) + pad
.left
;
368 {"x": pc2AxisMid
, "y": pad
.top
},
369 {"x": pc2AxisMid
, "y": pad
.top
+ height
}
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");
395 .attr("class", "PC1 axis")
396 .attr("transform", "translate(" + pad
.left
+ "," + (pad
.top
+ height
) +")")
402 .attr("transform", "rotate(90)")
403 .attr("fill", "green")
404 .style({"text-anchor":"start", "fill": "#86B404"});
407 .attr("class", "PC2 axis")
408 .attr("transform", "translate(" + pad
.left
+ "," + pad
.top
+ ")")
413 .attr("fill", "green")
414 .style("fill", "#86B404");
417 .attr("id", "pc1_axis_label")
419 .text("PC1: " + variances
[0][1] + "%" )
420 .attr("y", pad
.top
+ height
+ 55)
422 .attr("font-size", 12)
423 .style("fill", "green")
426 .attr("id", "pc2_axis_label")
428 .text("PC2: " + variances
[1][1] + "%" )
429 .attr("transform", "rotate(-90)")
431 .attr("x", -((pad
.left
+ height
/2) + 10))
432 .attr("font-size", 12)
433 .style("fill", "red")
435 var grpColor
= d3
.scale
.category10();
442 .style("fill", function(d
) {return grpColor(d
[0].trial
); })
444 .attr("cx", function(d
) {
447 return (pad
.left
+ (width
/2)) + pc1AxisScale(xVal
);
449 return (pad
.left
+ (width
/2)) - (-1 * pc1AxisScale(xVal
));
452 .attr("cy", function(d
) {
456 return ( pad
.top
+ (height
/2)) - pc2AxisScale(yVal
);
458 return (pad
.top
+ (height
/2)) + (-1 * pc2AxisScale(yVal
));
461 .on("mouseover", function(d
) {
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
) {
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");
489 if ( plotData
.pop_id
) {
490 id
= plotData
.pop_id
;
492 id
= plotData
.list_id
;
496 if (plotData
.list_name
) {
497 popName
= ' -- ' + plotData
.list_name
;
501 if (plotData
.pop_id
) {
502 pcaDownload
= "/download/pca/scores/population/" + id
;
506 .attr("xlink:href", pcaDownload
)
508 .text("[ Download PCA scores ]" + popName
)
509 .attr("y", pad
.top
+ height
+ 75)
511 .attr("font-size", 14)
512 .style("fill", "#954A09")
516 if (plotData
.output_link
) {
517 shareLink
= plotData
.output_link
;
521 .attr("xlink:href", shareLink
)
523 .text("[Share plot ]")
524 .attr("y", pad
.top
+ height
+ 100)
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
= [];
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
]);
552 var legend
= pcaPlot
.append("g")
553 .attr("class", "cell")
554 .attr("transform", "translate(" + (width
+ 60) + "," + (height
* 0.25) + ")")
558 legend
= legend
.selectAll("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")
579 .attr("fill", "#523CB5")
580 .style("fill", "#523CB5")
582 .attr("y", function (d
) { return 1 + (d
[0] * recLH
) + (d
[0] * 5); })
586 .attr("dominant-baseline", "middle")
587 .attr("text-anchor", "start");