Merge pull request #5205 from solgenomics/topic/generic_trial_upload
[sgn.git] / js / source / legacy / solGS / pca.js
blob202f8b5db4c453d322c0d214b1d2d0da7609cc29
1 /**
2  * Principal component analysis and scores plotting
3  * using d3js
4  * Isaak Y Tecle <iyt2@cornell.edu>
5  *
6  */
8 var solGS = solGS || function solGS() {};
10 solGS.pca = {
11   canvas: "#pca_canvas",
12   pcaPlotDivPrefix: "#pca_plot",
13   pcaMsgDiv: "#pca_message",
14   pcaPopsDiv: "#pca_pops_select_div",
15   pcaPopsSelectMenuId: "#pca_pops_select",
16   pcaPopsDataDiv: "#pca_pops_data_div",
18   getPcaArgs: function () {
19     var page = location.pathname;
20     var protocolId = solGS.genotypingProtocol.getGenotypingProtocolId("pca_div");
21     var dataType = this.getSelectedDataType();
23     if (page.match(/pca\/analysis/)) {
24       pcaArgs = this.getPcaArgsFromUrl();
25     } else {
26       var pcaPopId;
27       var trainingPopId;
28       var selectionPopId;
29       var dataType;
30       var dataStr;
32       var trainingPopId = jQuery("#training_pop_id").val();
33       if (page.match(/solgs\/trait\/|solgs\/model\/combined\/trials\/|\/breeders\/trial\//)) {
34         if (!trainingPopId) {
35           trainingPopId = jQuery("#trial_id").val();
36         }
37         pcaPopId = trainingPopId;
38       } else if (page.match(/\/selection\/|\/prediction\//)) {
39         selectionPopId = jQuery("#selection_pop_id").val();
40         pcaPopId = selectionPopId;
41       } else if (page.match(/solgs\/traits\/all\/population\/|models\/combined\/trials\//)) {
42         pcaPopId = trainingPopId;
43       }
45       var traitId = jQuery("#trait_id").val();
47       if (page.match(/combined/)) {
48         var dataSetType = "combined_populations";
49         var comboPopsId = trainingPopId;
50         if (comboPopsId) {
51           var dataSetType = "combined_populations";
52         }
53       }
55       pcaArgs = {
56         pca_pop_id: pcaPopId,
57         training_pop_id: trainingPopId,
58         combo_pops_id: comboPopsId,
59         selection_pop_id: selectionPopId,
60         data_structure: dataStr,
61         data_type: dataType,
62         data_set_type: dataSetType,
63         genotyping_protocol_id: protocolId,
64         trait_id: traitId,
65         analysis_type: "pca analysis",
66       };
67     }
69     return pcaArgs;
70   },
72   getPcaArgsFromUrl: function () {
73     var page = location.pathname;
74     if (page == "/pca/analysis/") {
75       page = "/pca/analysis";
76     }
78     var urlArgs = page.replace("/pca/analysis", "");
80     var pcaPopId;
81     var traitId;
82     var protocolId;
84     if (urlArgs) {
85       var args = urlArgs.split(/\/+/);
86       if (urlArgs.match(/trait/)) {
87         pcaPopId = args[1];
88         traitId = args[3];
89         protocolId = args[5];
90       } else {
91         pcaPopId = args[1];
92         protocolId = args[3];
93       }
95       var dataType;
96       if (protocolId) {
97         dataType = "genotype";
98       } else {
99         dataType = "phenotype";
100       }
102       var dataStr;
103       var listId;
104       var datasetId;
106       if (pcaPopId.match(/dataset/)) {
107         dataStr = "dataset";
108         datasetId = pcaPopId.replace(/dataset_/, "");
109       } else if (pcaPopId.match(/list/)) {
110         dataStr = "list";
111         listId = pcaPopId.replace(/list_/, "");
112       }
114       var args = {
115         pca_pop_id: pcaPopId,
116         list_id: listId,
117         trait_id: traitId,
118         dataset_id: datasetId,
119         data_structure: dataStr,
120         data_type: dataType,
121         genotyping_protocol_id: protocolId,
122       };
124       var reg = /\d+-+\d+/;
125       if (pcaPopId.match(reg)) {
126         var ids = pcaPopId.split("-");
127         args["training_pop_id"] = ids[0];
128         args["selection_pop_id"] = ids[1];
129       }
130       return args;
131     } else {
132       return {};
133     }
134   },
136   getRunPcaId: function (pcaPopId) {
137     if (pcaPopId) {
138       return `run_pca_${pcaPopId}`;
139     } else {
140       return "run_pca";
141     }
142   },
145   getSelectedPopPcaArgs: function (runPcaElemId) {
146     var pcaArgs;
148     var selectedPopDiv = document.getElementById(runPcaElemId);
149     if (selectedPopDiv) {
150       var selectedPopData = selectedPopDiv.dataset;
152       var selectedPop = JSON.parse(selectedPopData.selectedPop);
153       pcaPopId = selectedPop.pca_pop_id;
155       var dataType = this.getSelectedDataType(pcaPopId);
156       var pcaUrl = this.generatePcaUrl(pcaPopId);
158       pcaArgs = selectedPopData.selectedPop;
159       pcaArgs = JSON.parse(pcaArgs);
160       pcaArgs["data_type"] = dataType;
161       pcaArgs["analysis_page"] = pcaUrl;
162     }
164     return pcaArgs;
165   },
168   getPcaPopId: function (selectedId, dataStr) {
170     var pcaPopId;
171     if (dataStr) {
172       pcaPopId = `${dataStr}_${selectedId}`;
173     } else {
174       pcaPopId = selectedId;
175     }
177     return pcaPopId;
178   },
180   createRowElements: function (pcaPop) {
181     var popId = pcaPop.id;
182     var popName = pcaPop.name;
183     var dataStr = pcaPop.data_str;
185     var pcaPopId = solGS.pca.getPcaPopId(popId, dataStr);
186    
187     var dataTypes;
188     if (location.pathname.match(/pca\/analysis/)) {
189       dataTypes = pcaPop.data_type;
190     } else {
191       dataTypes = this.getDataTypeOpts();
192     }
194     var dataTypeOpts = this.createDataTypeSelect(dataTypes, pcaPopId);
195     
196     var runPcaBtnId = this.getRunPcaId(pcaPopId);
198     var listId;
199     var datasetId;
201     if (dataStr.match(/dataset/)) {
202       datasetId = popId;
203     } else if (dataStr.match(/list/)) {
204       listId = popId;
205     }
206     var protocolId = solGS.genotypingProtocol.getGenotypingProtocolId("pca_div");
208     var pcaArgs = {
209       pca_pop_id: pcaPopId,
210       data_structure: dataStr,
211       dataset_id: datasetId,
212       list_id: listId,
213       pca_pop_name: popName,
214       genotyping_protocol_id: protocolId,
215       analysis_type: "pca analysis",
216     };
218     pcaArgs = JSON.stringify(pcaArgs);
220     var runPcaBtn =
221       `<button type="button" id=${runPcaBtnId}` +
222       ` class="btn btn-success" data-selected-pop='${pcaArgs}'>Run PCA</button>`;
224     if (dataStr.match(/dataset/)) {
225       popName = `<a href="/dataset/${popId}">${popName}</a>`;
226     }
227     var rowData = [popName,
228       dataStr, pcaPop.owner, dataTypeOpts, runPcaBtn, `${dataStr}_${popId}`];
230     return rowData;
231   },
233   displayPcaPopsTable: function (tableId, data) {
235     var table = jQuery(`#${tableId}`).DataTable({
236       'searching': true,
237       'ordering': true,
238       'processing': true,
239       'paging': true,
240       'info': false,
241       'pageLength': 5,
242       'rowId': function (a) {
243         return a[5]
244       }
245     });
247     table.rows.add(data).draw();
249   },
252   getPcaPopsRows: function(pcaPops) {
254     var pcaPopsRows = [];
256     for (var i = 0; i < pcaPops.length; i++) {
257       if (pcaPops[i]) {
258         var pcaPopRow = this.createRowElements(pcaPops[i]);
259         pcaPopsRows.push(pcaPopRow);
260       }
261     }
263     return pcaPopsRows;
265   },
267   getPcaPops: function () {
269     var list = new solGSList();
270     var lists = list.getLists(["accessions", "plots", "trials"]);
271     lists = list.addDataStrAttr(lists);
272     lists = list.addDataTypeAttr(lists);
273     var datasets = solGS.dataset.getDatasetPops(["accessions", "trials"]);
274     datasets = solGS.dataset.addDataTypeAttr(datasets);
275     var pcaPops = [lists, datasets];
277     return pcaPops.flat();
279   },
281   getDataTypeOpts: function () {
282     
283     var dataTypeOpts = [];
284    
285     if (location.pathname.match(/breeders\/trial/)) {
286       dataTypeOpts = ["Genotype", "Phenotype"];
287     } else if (page.match(/solgs\/trait\/\d+\/population\/|solgs\/model\/combined\/trials\//)) {
288       dataTypeOpts = ["Genotype"];
289     } 
290     
291     return dataTypeOpts;
292   },
294   checkCachedPca: function (pcaArgs) {
295     if (document.URL.match(/pca\/analysis/)) {
296       var message = this.validatePcaParams(pcaArgs);
298       if (message) {
299         jQuery(this.pcaMsgDiv).prependTo(jQuery(this.canvas)).html(message).show().fadeOut(9400);
300       }
301     }
302     var page = pcaArgs.analysis_page;
303     pcaArgs = JSON.stringify(pcaArgs);
305     var checkCache = jQuery.ajax({
306       type: "POST",
307       dataType: "json",
308       data: {
309         page: page,
310         arguments: pcaArgs,
311       },
312       url: "/solgs/check/cached/result/",
313     });
315     return checkCache;
316   },
318   pcaDataTypeSelectId: function (pcaPopId) {
319     if (location.pathname.match(/pca\/analysis/) && pcaPopId) {
320       return `pca_data_type_select_${pcaPopId}`;
321     } else {
322       return "pca_data_type_select";
323     }
324   },
326   getSelectedDataType: function (pcaPopId) {
327     var dataType;
328     if (pcaPopId) {
329       var pcaDataSelectedId = this.pcaDataTypeSelectId(pcaPopId);
330       dataType = jQuery("#" + pcaDataSelectedId).val();
331     } else {
332       dataType = jQuery("#pca_data_type_select").val();
333     }
335     return dataType;
336   },
338   runPcaAnalysis: function (pcaArgs) {
339     pcaArgs = JSON.stringify(pcaArgs);
341     var pcaAnalysis = jQuery.ajax({
342       type: "POST",
343       dataType: "json",
344       data: {
345         arguments: pcaArgs,
346       },
347       url: "/run/pca/analysis",
348     });
350     return pcaAnalysis;
351   },
353   validatePcaParams: function (valArgs) {
354     var dataType = valArgs.data_type;
355     var dataStr = valArgs.data_structure;
356     var pcaPopId = valArgs.pca_pop_id;
357     var msg;
359     if (dataStr && dataStr.match("list")) {
360       var listId = pcaPopId.replace(/\w+_/, "");
361       var list = new CXGN.List();
362       var listType = list.getListType(listId);
364       if (listType.match(/accessions/) && dataType.match(/phenotype/i)) {
365         msg = "With list of clones, you can only do PCA based on <em>genotype</em>.";
366       }
368       if (listType.match(/plots/) && dataType.match(/genotype/i)) {
369         msg = "With list of plots, you can only do PCA based on <em>phenotype</em>.";
370       }
371     }
373     return msg;
374   },
376   createTable: function (tableId) {
378     var pcaTable =
379       `<table id="${tableId}" class="table table-striped"><thead><tr>` +
380       "<th>Population</th>" +
381       "<th>Data structure type</th>" +
382       "<th>Ownership</th>" +
383       "<th>Data type</th>" +
384       "<th>Run PCA</th>" +
385       "</tr></thead></table>";
387     return pcaTable;
388   },
392   createDataTypeSelect: function (opts, pcaPopId) {
393     var pcaDataTypeId = this.pcaDataTypeSelectId(pcaPopId);
394     var dataTypeGroup = '<select class="form-control" id="' + pcaDataTypeId + '">';
396     for (var i = 0; i < opts.length; i++) {
397       dataTypeGroup += '<option value="' + opts[i] + '">' + opts[i] + "</option>";
398     }
399     dataTypeGroup += "</select>";
401     return dataTypeGroup;
402   },
405   pcaDownloadLinks: function (res) {
406     var screePlotFile = res.scree_plot_file;
407     var scoresFile = res.scores_file;
408     var loadingsFile = res.loadings_file;
409     var variancesFile = res.variances_file;
411     var screePlot = screePlotFile.split("/").pop();
412     var screePlotLink = '<a href="' + screePlotFile + '" download=' + screePlot + ">Scree plot</a>";
414     var scores = scoresFile.split("/").pop();
415     var scoresLink = '<a href="' + scoresFile + '" download=' + scores + "> Scores </a>";
417     var loadings = loadingsFile.split("/").pop();
418     var loadingsLink = '<a href="' + loadingsFile + '" download=' + loadings + ">Loadings</a>";
420     var variances = variancesFile.split("/").pop();
421     var variancesLink = '<a href="' + variancesFile + '" download=' + variances + ">Variances</a>";
423     var pcaPlotDiv = this.pcaPlotDivPrefix.replace(/#/, '');
424     var plotId = res.file_id;
425     var pcaDownloadLinkId = `download_${pcaPlotDiv}_${plotId}`;
426     var pcaPlotLink =
427       `<a href='#'  onclick='event.preventDefault();' id='${pcaDownloadLinkId}'>PCA plot</a>`;
429     var downloadLinks =
430       screePlotLink +
431       " | " +
432       scoresLink +
433       " | " +
434       variancesLink +
435       " | " +
436       loadingsLink +
437       " | " +
438       pcaPlotLink;
440     return downloadLinks;
441   },
443   structurePlotData: function (res) {
444     var listId = res.list_id;
445     var listName;
447     if (listId) {
448       var list = new CXGN.List();
449       listName = list.listNameById(listId);
450       res['list_id'] = listId;
451       res['list_name'] = listName;
452     }
454     return res;
455   },
457   generatePcaUrl: function (pcaPopId) {
458     var traitId = jQuery("#trait_id").val();
459     var protocolId = solGS.genotypingProtocol.getGenotypingProtocolId("pca_div");
461     var solgsPages =
462       "solgs/population/" +
463       "|solgs/populations/combined/" +
464       "|solgs/trait/" +
465       "|solgs/model/combined/trials/" +
466       "|solgs/selection/\\d+|\\w+_\\d+/model/" +
467       "|solgs/combined/model/\\d+|\\w+_\\d+/selection/" +
468       "|solgs/models/combined/trials/" +
469       "|solgs/traits/all/population/";
471     var url = "/pca/analysis/" + pcaPopId;
473     var dataType;
474     if (location.pathname.match(solgsPages)) {
475       url = url + "/trait/" + traitId;
476     }
478     var pcaDataSelectedId = this.pcaDataTypeSelectId(pcaPopId);
479     dataType = jQuery("#" + pcaDataSelectedId).val();
481     if (dataType.match(/genotype/i)) {
482       url = url + "/gp/" + protocolId;
483     }
485     return url;
486   },
488   cleanUpOnSuccess: function (pcaPopId) {
490     jQuery(this.pcaMsgDiv).empty();
491     jQuery(`${this.canvas} .multi-spinner-container`).hide();
492     jQuery(`#${this.getRunPcaId(pcaPopId)}`).show();
494   },
496   feedBackOnFailure: function (pcaPopId, msg) {
497     jQuery(`${this.canvas} .multi-spinner-container`).hide();
499     jQuery(this.pcaMsgDiv)
500       .html(msg)
501       .fadeOut(8400);
503       jQuery(`#${this.getRunPcaId(pcaPopId)}`).show();
505   },
508   plotPca: function (plotData, downloadLinks) {
509     var scores = plotData.scores;
510     var variances = plotData.variances;
511     var loadings = plotData.loadings;
512     var trialsNames = plotData.trials_names;
514     var pc12 = [];
515     var pc1 = [];
516     var pc2 = [];
517     var trials = [];
519     jQuery.each(scores, function (i, pc) {
520       pc12.push([
521         {
522           name: pc[0],
523           pc1: parseFloat(pc[2]),
524           pc2: parseFloat(pc[3]),
525           trial: pc[1],
526         },
527       ]);
528       pc1.push(parseFloat(pc[2]));
529       pc2.push(parseFloat(pc[3]));
531       if (!trials.includes(pc[1])) {
532         trials.push(pc[1]);
533       }
534     });
536     var height = 400;
537     var width = 400;
538     var pad = {
539       left: 60,
540       top: 20,
541       right: 40,
542       bottom: 20,
543     };
544     var totalH = height + pad.top + pad.bottom + 100;
545     var totalW = width + pad.left + pad.right + 400;
547     var pcaCanvasDivId = this.canvas;
548     var pcaPlotPopId = plotData.file_id; 
549     var pcaPlotDivId = `${this.pcaPlotDivPrefix}_${pcaPlotPopId}`;
551     pcaPlotDivId = pcaPlotDivId.replace(/#/, "");
552     jQuery(pcaCanvasDivId).append("<div id=" + pcaPlotDivId + "></div>");
554     pcaPlotDivId = "#" + pcaPlotDivId;
555     var svg = d3
556       .select(pcaPlotDivId)
557       .insert("svg", ":first-child")
558       .attr("width", totalW)
559       .attr("height", totalH);
561     var pcaPlot = svg.append("g")
562       .attr("id", pcaPlotDivId)
563       .attr("transform", "translate(0,0)");
565     var pc1Min = d3.min(pc1);
566     var pc1Max = d3.max(pc1);
568     var pc1Limits = d3.max([Math.abs(d3.min(pc1)), d3.max(pc1)]);
569     var pc2Limits = d3.max([Math.abs(d3.min(pc2)), d3.max(pc2)]);
571     var pc1AxisScale = d3.scaleLinear()
572       .domain([0, pc1Limits])
573       .range([0, width / 2]);
575     var pc1AxisLabel = d3.scaleLinear()
576       .domain([-1 * pc1Limits, pc1Limits])
577       .range([0, width]);
579     var pc2AxisScale = d3.scaleLinear()
580       .domain([0, pc2Limits])
581       .range([0, height / 2]);
583     var pc1Axis = d3.axisBottom(pc1AxisLabel).tickSize(3);
585     var pc2AxisLabel = d3.scaleLinear()
586       .domain([-1 * pc2Limits, pc2Limits])
587       .range([height, 0]);
590       var pc2Axis = d3.axisLeft(pc2AxisLabel).tickSize(3);
592     var pc1AxisMid = 0.5 * height + pad.top;
593     var pc2AxisMid = 0.5 * width + pad.left;
595     var verMidLineData = [
596       {
597         x: pc2AxisMid,
598         y: pad.top,
599       },
600       {
601         x: pc2AxisMid,
602         y: pad.top + height,
603       },
604     ];
606     var rightNudge = 5;
607     var horMidLineData = [
608       {
609         x: pad.left,
610         y: pad.top + height / 2,
611       },
612       {
613         x: pad.left + width + rightNudge,
614         y: pad.top + height / 2,
615       },
616     ];
618     var lineFunction = d3.line()
619       .x(function (d) {
620         return d.x;
621       })
622       .y(function (d) {
623         return d.y;
624       });
625       // .interpolate("linear");
627     var pc1Color = "green";
628     var pc2Color = "red";
629     var axisValColor = "#86B404";
630     var labelFs = 12;
632     pcaPlot
633       .append("g")
634       .attr("class", "PC1 axis")
635       .attr("transform", "translate(" + pad.left + "," + (pad.top + height) + ")")
636       .call(pc1Axis)
637       .selectAll("text")
638       .attr("y", 0)
639       .attr("x", 10)
640       .attr("dy", ".1em")
641       .attr("transform", "rotate(90)")
642       .attr("fill", pc1Color)
643       .style({
644         "text-anchor": "start",
645         fill: axisValColor,
646       });
648     pcaPlot
649       .append("g")
650       .attr("transform", "translate(" + pc1AxisMid + "," + height + ")")
651       .append("text")
652       .text("PC1 (" + variances[0][1] + "%)")
653       .attr("y", pad.top + 40)
654       .attr("x", 0)
655       .attr("font-size", labelFs)
656       .style("fill", pc1Color);
658     pcaPlot
659       .append("g")
660       .attr("transform", "translate(" + pad.left + "," + pc2AxisMid + ")")
661       .append("text")
662       .text("PC2 (" + variances[1][1] + "%)")
663       .attr("y", -40)
664       .attr("x", 0)
665       .attr("transform", "rotate(-90)")
666       .attr("font-size", labelFs)
667       .style("fill", pc2Color);
669     pcaPlot
670       .append("g")
671       .attr("class", "PC2 axis")
672       .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
673       .call(pc2Axis)
674       .selectAll("text")
675       .attr("y", 0)
676       .attr("x", -10)
677       .style("fill", axisValColor);
679     pcaPlot
680       .append("path")
681       .attr("d", lineFunction(verMidLineData))
682       .attr("stroke", pc2Color)
683       .attr("stroke-width", 1)
684       .attr("fill", "none");
686     pcaPlot
687       .append("path")
688       .attr("d", lineFunction(horMidLineData))
689       .attr("stroke", pc1Color)
690       .attr("stroke-width", 1)
691       .attr("fill", "none");
693     var grpColor = d3.scaleOrdinal(d3.schemeCategory10);
694     pcaPlot.append("g")
695       .selectAll("circle")
696       .data(pc12)
697       .enter()
698       .append("circle")
699       .style("fill", function (d) {
700         return grpColor(trials.indexOf(d[0].trial));
701       })
702       .attr("r", 3)
703       .attr("cx", function (d) {
704         var xVal = d[0].pc1;
705         if (xVal >= 0) {
706           return pad.left + width / 2 + pc1AxisScale(xVal);
707         } else {
708           return pad.left + width / 2 - -1 * pc1AxisScale(xVal);
709         }
710       })
711       .attr("cy", function (d) {
712         var yVal = d[0].pc2;
714         if (yVal >= 0) {
715           return pad.top + height / 2 - pc2AxisScale(yVal);
716         } else {
717           return pad.top + height / 2 + -1 * pc2AxisScale(yVal);
718         }
719       })
720       .on("mouseover", function (d) {
721         d3.select(this).attr("r", 5).style("fill", axisValColor);
722         pcaPlot
723           .append("text")
724           .attr("id", "dLabel")
725           .style("fill", axisValColor)
726           .text(d[0].name + "(" + d[0].pc1 + "," + d[0].pc2 + ")")
727           .attr("x", width + pad.left + rightNudge)
728           .attr("y", height / 2);
729       })
730       .on("mouseout", function (d) {
731         d3.select(this)
732           .attr("r", 3)
733           .style("fill", function (d) {
734             return grpColor(trials.indexOf(d[0].trial));
735           });
736         d3.selectAll("text#dLabel").remove();
737       });
739     pcaPlot
740       .append("rect")
741       .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
742       .attr("height", height)
743       .attr("width", width + rightNudge)
744       .attr("fill", "none")
745       .attr("stroke", "#523CB5")
746       .attr("stroke-width", 1)
747       .attr("pointer-events", "none");
749     var popName = "";
750     if (plotData.list_name) {
751       popName = plotData.list_name;
752     }
754     popName = popName ? popName + " (" + plotData.data_type + ")" : " (" + plotData.data_type + ")";
755     var dld = "Download PCA " + popName + ": ";
757     if (downloadLinks) {
758       jQuery(pcaPlotDivId).append('<p style="margin-left: 40px">' + dld + downloadLinks + "</p>");
759     }
761     if (trialsNames && Object.keys(trialsNames).length > 1) {
762       var trialsIds = jQuery.uniqueSort(trials);
763       trialsIds = jQuery.uniqueSort(trialsIds);
765       var legendValues = [];
766       var cnt = 0;
767       var allTrialsNames = [];
769       for (var tr in trialsNames) {
770         allTrialsNames.push(trialsNames[tr]);
771       }
773       trialsIds.forEach(function (id) {
774         var groupName = [];
776         if (id.match(/\d+-\d+/)) {
777           var ids = id.split("-");
779           ids.forEach(function (id) {
780             groupName.push(trialsNames[id]);
781           });
783           groupName = "common: " + groupName.join(",");
784         } else {
785           groupName = trialsNames[id];
786         }
787         legendValues.push([cnt, id, groupName]);
788         cnt++;
789       });
791       var recLH = 20;
792       var recLW = 20;
793       var legendXOrig = pad.left + 10 + width;
794       var legendYOrig = height * 0.25;
796       var legend = pcaPlot
797         .append("g")
798         .attr("class", "cell")
799         .attr("transform", "translate(" + legendXOrig + "," + legendYOrig + ")")
800         .attr("height", 100)
801         .attr("width", 100);
803       legend = legend
804         .selectAll("rect")
805         .data(legendValues)
806         .enter()
807         .append("rect")
808         .attr("x", function (d) {
809           return 1;
810         })
811         .attr("y", function (d) {
812           return 1 + d[0] * recLH + d[0] * 5;
813         })
814         .attr("width", recLH)
815         .attr("height", recLW)
816         .style("stroke", "black")
817         .style("fill", function (d) {
818           return grpColor(trials.indexOf(d[1]));
819         });
821       var legendTxt = pcaPlot
822         .append("g")
823         .attr(
824           "transform",
825           "translate(" + (legendXOrig + 30) + "," + (legendYOrig + 0.5 * recLW) + ")"
826         )
827         .attr("id", "legendtext");
829       legendTxt
830         .selectAll("text")
831         .data(legendValues)
832         .enter()
833         .append("text")
834         .attr("fill", "#523CB5")
835         .style("fill", "#523CB5")
836         .attr("x", 1)
837         .attr("y", function (d) {
838           return 1 + d[0] * recLH + d[0] * 5;
839         })
840         .text(function (d) {
841           return d[2];
842         })
843         .attr("dominant-baseline", "middle")
844         .attr("text-anchor", "start");
845     }
846   },
848   ////////
850 /////
852 jQuery(document).ready(function () {
853   var url = location.pathname;
854   var canvas = solGS.pca.canvas;
856   if (url.match(/pca\/analysis/)) {
858     var pcaArgs = solGS.pca.getPcaArgsFromUrl();
859     var pcaPopId = pcaArgs.pca_pop_id;
860     if (pcaPopId) {
861       if (pcaArgs.data_structure && !pcaPopId.match(/list|dataset/)) {
862         pcaArgs["pca_pop_id"] = pcaArgs.data_structure + "_" + pcaPopId;
863       }
864       pcaArgs["analysis_page"] = url;
866       solGS.pca.checkCachedPca(pcaArgs).done(function (res) {
867       
868         if (res.scores) {
869           var plotData = solGS.pca.structurePlotData(res);
870           var downloadLinks = solGS.pca.pcaDownloadLinks(res);
871           solGS.pca.plotPca(plotData, downloadLinks);
872         }
873       });
874     }
875   }
878 jQuery(document).ready(function () {
879   var canvas = solGS.pca.canvas;
880   
881   jQuery(canvas).on("click", "a", function (e) {
882     var linkId = e.target.id;
883     var pcaPlotId = linkId.replace(/download_/, "");
885     if (pcaPlotId.match(/pca_plot_/)) {
886       saveSvgAsPng(document.getElementById("#" + pcaPlotId), pcaPlotId + ".png", { scale: 2 });
887     }
888   });
892 jQuery(document).ready(function () {
893   var url = location.pathname;
895   if (url.match(/solgs\/selection\/|solgs\/combined\/model\/\d+\/selection\//)) {
896     jQuery("#pca_data_type_select").html('<option selected="genotype">Genotype</option>');
897   }
900 jQuery(document).ready(function () {
901   jQuery("#pca_div").on("click", function (e) {
902     var runPcaBtnId = e.target.id;
903     if (runPcaBtnId.match(/run_pca/)) {
904     
905       var  pcaArgs;
906       if (document.URL.match(/pca\/analysis\//)) {
907         pcaArgs = solGS.pca.getSelectedPopPcaArgs(runPcaBtnId);
908       } else {
909         pcaArgs = solGS.pca.getPcaArgs();
910       }
912       pcaPopId = pcaArgs.pca_pop_id;
913       var canvas = solGS.pca.canvas;
914       var pcaMsgDiv = solGS.pca.pcaMsgDiv;
915       var pcaUrl = solGS.pca.generatePcaUrl(pcaPopId);
916       pcaArgs["analysis_page"] = pcaUrl;
918       solGS.pca
919         .checkCachedPca(pcaArgs)
920         .done(function (res) {
921           if (res.scores) {
922             var plotData = solGS.pca.structurePlotData(res);
923             var downloadLinks = solGS.pca.pcaDownloadLinks(res);
924             solGS.pca.plotPca(plotData, downloadLinks);
926              solGS.pca.cleanUpOnSuccess(pcaPopId);
927           } else {
928             var page = location.pathname;
929             var pcaUrl = solGS.pca.generatePcaUrl(pcaArgs.pca_pop_id);
930             pcaArgs["analysis_page"] = pcaUrl;
932             runPcaBtnId = `#${runPcaBtnId}`;
933             
934             var title =
935               "<p>This analysis may take a long time. " +
936               "Do you want to submit the analysis and get an email when it completes?</p>";
938             var jobSubmit = '<div id= "pca_submit">' + title + "</div>";
940             jQuery(jobSubmit).appendTo("body");
942             jQuery("#pca_submit").dialog({
943               height: "auto",
944               width: "auto",
945               modal: true,
946               title: "pca job submission",
947               buttons: {
948                 OK: {
949                   text: "Yes",
950                   class: "btn btn-success",
951                   id: "queue_job",
952                   click: function () {
953                     jQuery(this).dialog("close");
954                     solGS.submitJob.checkUserLogin(pcaUrl, pcaArgs);
955                   },
956                 },
958                 No: {
959                   text: "No, I will wait till it completes.",
960                   class: "btn btn-warning",
961                   id: "no_queue",
962                   click: function () {
963                     jQuery(this).dialog("close");
965                     jQuery(runPcaBtnId).hide();
967                     jQuery(`${canvas} .multi-spinner-container`).show();
968                     jQuery(pcaMsgDiv).html("Running pca... please wait...").show();
970                     solGS.pca
971                       .runPcaAnalysis(pcaArgs)
972                       .done(function (res) {
973                         if (res.scores) {
974                           var downloadLinks = solGS.pca.pcaDownloadLinks(res);
975                           var plotData = solGS.pca.structurePlotData(res);
976                           solGS.pca.plotPca(plotData, downloadLinks);
978                           solGS.pca.cleanUpOnSuccess(pcaPopId);
979                         } else {
980                           var msg = "There is no PCA output for this dataset.";
981                           solGS.pca.feedBackOnFailure(pcaPopId, msg);
982                         }
983                       jQuery(runPcaBtnId).show();
985                       })
986                       .fail(function (res) {
987                         var msg = "Error occured running the PCA.";
988                         solGS.pca.feedBackOnFailure(pcaPopId, msg);
989                         jQuery(runPcaBtnId).show();
990                       });
991                   },
992                 },
994                 Cancel: {
995                   text: "Cancel",
996                   class: "btn btn-info",
997                   id: "cancel_queue_info",
998                   click: function () {
999                     jQuery(this).dialog("close");
1000                   },
1001                 },
1002               },
1003             });
1004             jQuery(jobSubmit).show();
1006             jQuery("#queue_job").on("click", function (e) {
1007               solGS.submitJob.checkUserLogin(page, args);
1008             });
1010             jQuery("#queue_no").on("click", function (e) {
1011               solGS.pca
1012                 .runPcaAnalysis(pcaArgs)
1013                 .done(function (res) {
1014                   if (res.scores) {
1015                     var plotData = solGS.pca.structurePlotData(res);
1016                     var downloadLinks = solGS.pca.pcaDownloadLinks(res);
1017                     solGS.pca.plotPca(plotData, downloadLinks);
1019                    solGS.pca.cleanUpOnSuccess(pcaPopId);
1020                   } else {
1021                     var msg = "There is no PCA output for this dataset.";
1022                     solGS.pca.feedBackOnFailure(pcaPopId, msg);
1023                   }
1024                 })
1025                 .fail(function (res) {
1026                   var msg = "Error occured running the PCA.";
1027                         solGS.pca.feedBackOnFailure(pcaPopId,msg);
1028                 });
1030                 jQuery(runPcaBtnId).show();
1031             });
1032           }
1033         })
1034         .fail(function () {
1035           var msg = "Error occured checking for cached output.";
1036           solGS.pca.feedBackOnFailure(pcaPopId,msg);
1037         });
1038         jQuery(runPcaBtnId).show();
1039     }
1040   });
1043 jQuery(document).ready(function () {
1044   if (location.pathname.match(/pca\/analysis/)) {
1046     pcaPopsDataDiv = solGS.pca.pcaPopsDataDiv;
1047     var tableId = 'pca_pops_table';
1048     var pcaPopsTable = solGS.pca.createTable(tableId)
1049     jQuery(pcaPopsDataDiv).append(pcaPopsTable).show();
1051     var pcaPops = solGS.pca.getPcaPops();
1052     var pcaPopsRows = solGS.pca.getPcaPopsRows(pcaPops);
1054     solGS.pca.displayPcaPopsTable(tableId, pcaPopsRows);
1056     jQuery("#add_new_pops").show();
1058   }
1062 jQuery.fn.doesExist = function () {
1063   return jQuery(this).length > 0;