Merge pull request #5205 from solgenomics/topic/generic_trial_upload
[sgn.git] / js / source / legacy / solGS / cluster.js
blob44389c01fb4a50e2efc6ad346a94b32bbe867574
1 /**
2  * K-means and hierarchical cluster analysis and vizualization
3  * Isaak Y Tecle <iyt2@cornell.edu>
4  *
5  */
7 var solGS = solGS || function solGS() { };
9 solGS.cluster = {
10   canvas: "#cluster_canvas",
11   clusterPlotDiv: "#cluster_plot",
12   clusterMsgDiv: "#cluster_message",
13   clusterPopsDiv: "#cluster_pops_select_div",
14   clusterPopsSelectMenuId: "#cluster_pops_select",
15   clusterPopsDataDiv: "#cluster_pops_data_div",
17   getClusterArgsFromUrl: function () {
18     var page = location.pathname;
19     if (page == "/cluster/analysis/") {
20       page = "/cluster/analysis";
21     }
23     var urlArgs = page.replace("/cluster/analysis", "");
25     var clusterPopId;
26     var traitId;
27     var protocolId;
28     var kNumber;
29     var dataType;
30     var sIndexName;
31     var selectionProp;
32     var traitsCode;
33     var clusterType;
35     if (urlArgs) {
36       var args = urlArgs.split(/\/+/);
37       clusterPopId = args[1];
38       clusterType = args[3];
39       dataType = args[5];
40       kNumber = args[7];
42       if (urlArgs.match(/traits/)) {
43         traitsCode = args[9];
44       }
46       if (urlArgs.match(/selPop/)) {
47         selectionProp = args[11];
48       }
50       protocolId = args.pop();
52       if (!dataType.match(/phenotype|genotype|gebv/)) {
53         sIndexName = dataType;
54         dataType = "genotype";
55       }
57       var dataStr;
58       var listId;
59       var datasetId;
61       if (clusterPopId.match(/dataset/)) {
62         dataStr = "dataset";
63         datasetId = clusterPopId.replace(/dataset_/, "");
64       } else if (clusterPopId.match(/list/)) {
65         dataStr = "list";
66         listId = clusterPopId.replace(/list_/, "");
67       }
69       var args = {
70         cluster_pop_id: clusterPopId,
71         data_type: dataType,
72         k_number: kNumber,
73         sindex_name: sIndexName,
74         selection_proportion: selectionProp,
75         list_id: listId,
76         trait_id: traitId,
77         training_traits_code: traitsCode,
78         dataset_id: datasetId,
79         data_structure: dataStr,
80         genotyping_protocol_id: protocolId,
81         cluster_type: clusterType,
82       };
84       var reg = /\.+-\.+/;
85       if (clusterPopId.match(reg)) {
86         var ids = clusterPopId.split("-");
87         args["training_pop_id"] = ids[0];
88         args["selection_pop_id"] = ids[1];
89       }
90       return args;
91     } else {
92       return {};
93     }
94   },
96   getClusterPopId: function (selectedId, dataStr) {
98     var clusterPopId;
99     if (dataStr) {
100       clusterPopId = `${dataStr}_${selectedId}`;
101     } else {
102       clusterPopId = selectedId;
103     }
105     return clusterPopId;
106   },
108   createClusterTypeSelect: function (rowId) {
109     var clusterTypeId = this.clusterTypeSelectId(rowId);
110     var clusterTypeGroup =
111       '<div id="cluster_type_opts"><select class="form-control" id="' +
112       clusterTypeId +
113       '">' +
114       '<option value="k-means">K-Means</option>' +
115       '<option value="hierarchical">Hierarchical</option>' +
116       "</select></div>";
118     return clusterTypeGroup;
119   },
121   clusterTypeSelectId: function (rowId) {
122     if (location.pathname.match(/cluster\/analysis/) && rowId) {
123       return `cluster_type_select_${rowId}`;
124     } else {
125       return "cluster_type_select";
126     }
127   },
129   clusterDataTypeSelectId: function (rowId) {
130     if (location.pathname.match(/cluster\/analysis/) && rowId) {
131       return `cluster_data_type_select_${rowId}`;
132     } else {
133       return "cluster_data_type_select";
134     }
135   },
137   clusterKnumSelectId: function (rowId) {
138     if (location.pathname.match(/cluster\/analysis/) && rowId) {
139       return `k_number_input_${rowId}`;
140     } else {
141       return "k_number_input";
142     }
143   },
145   clusterSelPropSelectId: function (rowId) {
146     if (location.pathname.match(/cluster\/analysis/) && rowId) {
147       return `selection_proportion_input_${rowId}`;
148     } else {
149       return "selection_proportion_input";
150     }
151   },
153   getRunClusterBtnId: function (rowId) {
154     if (location.pathname.match(/cluster\/analysis/) && rowId) {
155       return `run_cluster_${rowId}`;
156     } else {
157       return "run_cluster";
158     }
159   },
161   createDataTypeSelect: function (opts, rowId) {
162     var clusterDataTypeId = this.clusterDataTypeSelectId(rowId);
163     var dataTypeGroup = '<select class="form-control" id="' + clusterDataTypeId + '">';
165     for (var i = 0; i < opts.length; i++) {
166       dataTypeGroup += '<option value="' + opts[i] + '">' + opts[i] + "</option>";
167     }
168     dataTypeGroup += "</select>";
170     return dataTypeGroup;
171   },
173   getDataTypeOpts: function (args) {
175     var popType;
176     if (args) {
177       popType = args.type;
178     }
180     var dataTypeOpts = [];
181     var page = location.pathname;
183     if (page.match(/breeders\/trial/)) {
184       dataTypeOpts = ["Genotype", "Phenotype"];
185     } else if (page.match(/solgs\/trait\/\d+\/population\/|solgs\/model\/combined\/trials\//)) {
186       dataTypeOpts = ["Genotype"];
187     } else {
188       if (!popType) {
189         popType = "undef";
190       }
192       if (popType.match(/^selection$/)) {
193         dataTypeOpts = ["Genotype", "GEBV"];
194       } else if (popType.match(/selection_index/)) {
195         dataTypeOpts = ["Genotype"];
196       } else {
197         dataTypeOpts = ["Genotype", "GEBV", "Phenotype"];
198       }
199     }
201     return dataTypeOpts;
202   },
204   createRowElements: function (clusterPop) {
205     var popId = clusterPop.id;
206     var popName = clusterPop.name;
207     var dataStr = clusterPop.data_str;
209     var clusterPopId = solGS.cluster.getClusterPopId(popId, dataStr);
210     var clusterTypeOpts = solGS.cluster.createClusterTypeSelect(clusterPopId);
212     var dataTypes;
213     if (location.pathname.match(/pca\/analysis/)) {
214       dataTypes = clusterPop.data_type;
215     } else {
216       dataTypes = this.getDataTypeOpts();
217     }
219     var dataTypeOpts = solGS.cluster.createDataTypeSelect(dataTypes, clusterPopId);
220     var kNumId = solGS.cluster.clusterKnumSelectId(clusterPopId);
221     var runClusterBtnId = solGS.cluster.getRunClusterBtnId(clusterPopId);
223     var kNum = '<input class="form-control" type="text" placeholder="3" id="' + kNumId + '"/>';
225     var clusterArgs = JSON.stringify(clusterPop);
227     var runClusterBtn =
228       `<button type="button" id=${runClusterBtnId}` +
229       ` class="btn btn-success" data-selected-pop='${clusterArgs}'>Run cluster</button>`;
231     if (dataStr.match(/dataset/)) {
232       popName = `<a href="/dataset/${popId}">${popName}</a>`;
233     }
234     var rowData = [popName,
235       dataStr, clusterPop.owner, clusterTypeOpts,
236       dataTypeOpts, kNum, runClusterBtn, `${dataStr}_${popId}`];
238     return rowData;
239   },
241   createTable: function (tableId) {
242     var table =
243       '<table class="table table-striped" id="' +
244       tableId +
245       '">' +
246       "<thead>" +
247       "<tr>" +
248       "<th>Name</th>" +
249       "<th>Data structure</th>" +
250       "<th>Ownership</th>" +
251       "<th>Clustering method</th>" +
252       "<th>Data type</th>" +
253       "<th>No. of  clusters (K)</th>" +
254       "<th>Run cluster</th>" +
255       "</tr>" +
256       "</thead></table>";
258     return table;
259   },
261   clusterResult: function (clusterArgs) {
262     var clusterPopId = clusterArgs.cluster_pop_id;
263     var clusterType = clusterArgs.cluster_type;
264     var kNumber = clusterArgs.k_number;
265     var dataType = clusterArgs.data_type;
266     var selectionProp = clusterArgs.selection_proportion;
267     var selectedId = clusterArgs.selected_id;
268     var selectedName = clusterArgs.selected_name;
269     var dataStr = clusterArgs.data_structure;
271     dataType = dataType.toLowerCase();
272     clusterType = clusterType.toLowerCase();
273     var protocolId = jQuery("#cluster_div #genotyping_protocol #genotyping_protocol_id").val();
275     if (!protocolId) {
276       protocolId = solGS.genotypingProtocol.getGenotypingProtocolId("cluster_div");
277     }
279     var trainingTraitsIds = jQuery("#training_traits_ids").val();
281     if (trainingTraitsIds) {
282       trainingTraitsIds = trainingTraitsIds.split(",");
283     }
285     if (!trainingTraitsIds) {
286       var traitId = jQuery("#trait_id").val();
287       trainingTraitsIds = [traitId];
288     }
290     if (trainingTraitsIds == "") {
291       trainingTraitsIds = [];
292     }
294     var popDetails = solGS.getPopulationDetails();
295     if (!popDetails) {
296       popDetails = {};
297     }
299     var popId;
300     var popType;
301     var popName;
303     var page = location.pathname;
304     if (!page.match(/cluster\/analysis/)) {
305       if (
306         page.match(/solgs\/trait\/\d+\/population\/|solgs\/model\/combined\/populations\/|breeders\//)
307       ) {
308         popId = popDetails.training_pop_id;
309         popName = popDetails.training_pop_name;
310         popType = "training";
311       } else if (page.match(/solgs\/selection\/|solgs\/model\/combined\/trials\//)) {
312         popId = popDetails.selection_pop_id;
313         popName = popDetails.selection_pop_name;
314         popType = "selection";
315       } else {
316         popId = jQuery("#cluster_selected_pop_id").val();
317         popType = jQuery("#cluster_selected_pop_type").val();
318         popName = jQuery("#cluster_selected_pop_name").val();
320       }
321     }
323     if (!selectedName) {
324       selectedName = popName;
325     }
327     if (!selectedId) {
328       selectedId = popId;
329     }
331     var validateArgs = {
332       data_id: selectedId,
333       data_structure: dataStr,
334       data_type: dataType,
335       selection_proportion: selectionProp,
336       pop_type: popType,
337     };
339     var message = this.validateClusterParams(validateArgs);
340     var url = location.pathname;
342     var clusterMsgDiv = this.clusterMsgDiv;
344     if (message != undefined) {
345       jQuery(clusterMsgDiv).html(message).show().fadeOut(9400);
346     } else {
347       if (url.match(/solgs\/models\/combined\/trials\//)) {
348         if (popType.match(/training/)) {
349           popDetails["combo_pops_id"] = popId;
350         } else if (popType.match(/selection/)) {
351           popDetails["selection_pop_id"] = popId;
352         }
353       }
355       var listId;
356       var datasetId;
357       var datasetName;
358       var sIndexName;
360       if (String(selectedId).match(/list/)) {
361         dataStr = "list";
362       } else if (String(selectedId).match(/dataset/)) {
363         dataStr = "dataset";
364       }
366       if (dataStr == "list") {
367         if (isNaN(selectedId)) {
368           listId = selectedId.replace("list_", "");
369         } else {
370           listId = selectedId;
371         }
372       } else if (dataStr == "dataset") {
373         if (isNaN(selectedId)) {
374           datasetId = selectedId.replace("dataset_", "");
375         } else {
376           datasetId = selectedId;
377         }
379         datasetName = selectedName;
380       }
382       if (!clusterPopId) {
383         if (url.match(/solgs\/trait\//)) {
384           clusterPopId = popDetails.training_pop_id;
385         } else if (url.match(/solgs\/selection\//)) {
386           clusterPopId = popDetails.selection_pop_id;
387         } else if (url.match(/combined/)) {
388           clusterPopId = jQuery("#combo_pops_id").val();
389         }
390       }
392       if (popType == "selection_index") {
393         sIndexName = selectedName;
394       }
396       var traitsCode;
398       var page;
399       var fileId = clusterPopId;
400       if (location.pathname.match(/cluster\/analysis/)) {
401         page =
402           "/cluster/analysis/" +
403           clusterPopId +
404           "/ct/" +
405           clusterType +
406           "/dt/" +
407           dataType +
408           "/k/" +
409           kNumber;
410         if (dataType.match(/genotype/i)) {
411           page = page + "/gp/" + protocolId;
412         }
413       } else {
414         traitsCode = jQuery("#training_traits_code").val();
415         if (
416           popType.match(/selection/) &&
417           location.pathname.match(/solgs\/traits\/all\/|solgs\/models\/combined\/trials\//)
418         ) {
419           popDetails["selection_pop_id"] = clusterPopId;
420           fileId = popDetails.training_pop_id + "-" + clusterPopId;
421         }
422         if (sIndexName) {
423           page =
424             "/cluster/analysis/" +
425             fileId +
426             "/ct/" +
427             clusterType +
428             "/dt/" +
429             sIndexName +
430             "/k/" +
431             kNumber;
432         } else {
433           page =
434             "/cluster/analysis/" +
435             fileId +
436             "/ct/" +
437             clusterType +
438             "/dt/" +
439             dataType +
440             "/k/" +
441             kNumber;
443           if (traitsCode) {
444             page = page + "/traits/" + traitsCode;
445           } else {
446             page = page + "/traits/" + "undefined";
447           }
449           if (selectionProp) {
450             page = page + "/sp/" + selectionProp;
451           } else {
452             page = page + "/sp/" + "undefined";
453           }
455           page = page + "/gp/" + protocolId;
456         }
457       }
458       var clusterArgs = {
459         training_pop_id: popDetails.training_pop_id,
460         selection_pop_id: popDetails.selection_pop_id,
461         combo_pops_id: popDetails.combo_pops_id,
462         training_traits_ids: trainingTraitsIds,
463         training_traits_code: traitsCode,
464         cluster_pop_id: clusterPopId,
465         list_id: listId,
466         cluster_type: clusterType,
467         data_structure: dataStr,
468         dataset_id: datasetId,
469         dataset_name: datasetName,
470         data_type: dataType,
471         k_number: kNumber,
472         selection_proportion: selectionProp,
473         sindex_name: sIndexName,
474         cluster_pop_name: selectedName || "",
475         genotyping_protocol_id: protocolId,
476         analysis_type: "cluster analysis",
477         analysis_page: page,
478       };
480       return clusterArgs;
481     }
482   },
484   checkCachedCluster: function (page, args) {
485     if (typeof args !== "string") {
486       args = JSON.stringify(args);
487     }
489     var checkCached = jQuery.ajax({
490       type: "POST",
491       dataType: "json",
492       data: {
493         page: page,
494         arguments: args,
495       },
496       url: "/solgs/check/cached/result/",
497     });
499     return checkCached;
501   },
503   runClusterAnalysis: function (clusterArgs) {
504     var clusterPopId;
505     if (typeof clusterArgs == "string") {
506       clusterArgs = JSON.parse(clusterArgs);
507       clusterType = clusterArgs.cluster_type;
508       clusterPopId = clusterArgs.cluster_pop_id;
509       runClusterBtnId = this.getRunClusterBtnId(clusterPopId);
510     } else {
511       clusterType = clusterArgs.cluster_type;
512       clusterPopId = clusterArgs.cluster_pop_id;
513       runClusterBtnId = this.getRunClusterBtnId(clusterPopId);
514     }
516     if (typeof clusterArgs !== "string") {
517       clusterArgs = JSON.stringify(clusterArgs);
518     }
520     var runAnalysis = jQuery.ajax({
521       type: "POST",
522       dataType: "json",
523       data: {
524         arguments: clusterArgs,
525       },
526       url: "/run/cluster/analysis",
527     });
529     return runAnalysis;
531   },
533   validateClusterParams: function (valArgs) {
534     var popType = valArgs.pop_type;
535     var dataType = valArgs.data_type;
536     var selectionProp = valArgs.selection_proportion;
537     var dataStr = valArgs.data_structure;
538     var dataId = valArgs.data_id;
539     var msg;
541     if (popType == "selection_index") {
542       if (dataType.match(/phenotype/i) || dataType.match(/gebv/i)) {
543         msg =
544           "K-means clustering for selection index type" + " data works with genotype data only.";
545       }
547       if (dataType.match(/genotype/i) != null && !selectionProp) {
548         msg =
549           "The selection proportion value is empty." +
550           " You need to define the fraction of the" +
551           " population you want to select.";
552       }
553     }
555     if (dataStr == "list") {
556       var list = new CXGN.List();
558       if (isNaN(dataId)) {
559         dataId = dataId.replace(/list_/, "");
560       }
562       var listType = list.getListType(dataId);
564       if (listType == "accessions" && dataType.match(/phenotype/i)) {
565         msg = "With list of clones, you can only cluster based on <em>genotype</em>.";
566       }
568       if (listType == "plots" && dataType.match(/genotype/i)) {
569         msg = "With list of plots, you can only cluster based on <em>phenotype</em>.";
570       }
571     }
573     return msg;
574   },
576   createClusterDownloadLinks: function (res) {
577     var popName = res.cluster_pop_name || "";
578     var clusterPlotFileName = res.cluster_plot.split("/").pop();
579     var plotType;
580     var outFileType;
581     var clustersFile;
582     var elbowPlotFile;
583     var kclusterMeansFile;
584     var kclusterVariancesFile;
586     if (clusterPlotFileName.match(/k-means/i)) {
587       plotType = "K-means plot";
588       outFileType = "Clusters";
589       clustersFile = res.kmeans_clusters;
590       elbowPlotFile = res.elbow_plot;
591       kclusterVariancesFile = res.kcluster_variances;
592       kclusterMeansFile = res.kcluster_means;
593     } else {
594       plotType = "Dendrogram";
595       outFileType = "Newick tree format";
596       clustersFile = res.newick_file;
597     }
599     var clustersFileName = clustersFile.split("/").pop();
600     var clustersLink =
601       '<a href="' + clustersFile + '" download=' + clustersFileName + '">' + outFileType + "</a>";
603     var elbowLink;
604     var kclusterMeansLink;
605     var kclusterVariancesLink;
607     if (elbowPlotFile) {
608       var elbowFileName = elbowPlotFile.split("/").pop();
609       elbowLink = '<a href="' + elbowPlotFile + '" download=' + elbowFileName + '">Elbow plot</a>';
611       if (kclusterMeansFile) {
612         var kclusterMeansFileName = kclusterMeansFile.split("/").pop();
613         kclusterMeansLink =
614           '<a href="' +
615           kclusterMeansFile +
616           '" download=' +
617           kclusterMeansFileName +
618           '">Cluster means</a>';
619       }
621       var kclusterVariancesFileName = kclusterVariancesFile.split("/").pop();
622       kclusterVariancesLink =
623         '<a href="' +
624         kclusterVariancesFile +
625         '" download=' +
626         kclusterVariancesFileName +
627         '">Cluster variances </a>';
628     }
630     var reportFile = res.cluster_report;
631     var reportFileName = reportFile.split("/").pop();
632     var dataType = res.data_type;
633     var reportLink =
634       '<a href="' + reportFile + '" download=' + reportFileName + '">Analysis Report </a>';
636     var downloadLinks = `<strong>Download 
637       ${popName} (${dataType})</strong>: 
638       ${clustersLink} | 
639       ${reportLink}`;
641     if (elbowPlotFile) {
642       if (kclusterMeansLink) {
643         downloadLinks += " | " + kclusterMeansLink;
644       }
646       downloadLinks += " | " + kclusterVariancesLink + " | " + elbowLink;
647     }
649     var clusterPlotDiv = this.clusterPlotDiv.replace(/#/, '');
650     var plotId = res.file_id;;
651     var clusterDownloadLinkId = `download_${clusterPlotDiv}_${plotId}`;
652     var clusterPlotDownload =
653       `<a href='#'  onclick='event.preventDefault();' id='${clusterDownloadLinkId}'>Cluster plot</a>`;
655     downloadLinks += " | " + clusterPlotDownload;
657     return downloadLinks;
658   },
660   displayStaticPlot: function(res, downloadLinks) {
661       var imageId = res.plot_name;
662       imageId = 'id="' + imageId + '"';
663       var plot = "<img " + imageId + ' src="' + res.cluster_plot + '">';
664   
665       jQuery("#cluster_plot").prepend('<p style="margin-top: 20px">' + downloadLinks + "</p>");
666       jQuery("#cluster_plot").prepend(plot);
667       //     // solGS.dendrogram.plot(res.json_data, '#cluster_canvas', '#cluster_plot', downloadLinks)
668     },
669   
671   displayClusterOutput: function (res) {
672     var downloadLinks = this.createClusterDownloadLinks(res);
673    
674     if (res.cluster_type.match(/k-means/i)) {
675       this.plotCluster(res, downloadLinks)
676     } else {
677       this.displayStaticPlot(res, downloadLinks);
678     }
679   
680   },
682   getClusterPopsTable: function (tableId) {
683     var clusterTable = this.createTable(tableId);
684     return clusterTable;
685   },
687   runCluster: function (selectedId, selectedName, dataStr) {
688     var clusterPopId = this.getClusterPopId(selectedId, dataStr);
689     var clusterOpts = solGS.cluster.clusteringOptions(clusterPopId);
690     var clusterType = clusterOpts.cluster_type || "k-means";
691     var kNumber = clusterOpts.k_number || 3;
692     var dataType = clusterOpts.data_type || "genotype";
694     var clusterArgs = {
695       selected_id: selectedId,
696       selected_name: selectedName,
697       data_structure: dataStr,
698       cluster_pop_id: clusterPopId,
699       cluster_type: clusterType,
700       data_type: dataType,
701       k_number: kNumber,
702     };
704     this.clusterResult(clusterArgs);
705   },
707   clusteringOptions: function (clusterPopId) {
708     var clusterTypeId = this.clusterTypeSelectId(clusterPopId);
709     var kNumId = this.clusterKnumSelectId(clusterPopId);
710     var dataTypeId = this.clusterDataTypeSelectId(clusterPopId);
711     var selectionPropId = this.clusterSelPropSelectId(clusterPopId);
713     var dataType = jQuery("#" + dataTypeId).val() || "genotype";
714     var clusterType = jQuery("#" + clusterTypeId).val() || "k-means";
715     var kNumber = jQuery("#" + kNumId).val() || 3;
716     var selectionProp = jQuery("#" + selectionPropId).val();
718     if (typeof kNumber === "string") {
719       kNumber = kNumber.replace(/\s+/g, "");
720     }
722     if (selectionProp) {
723       selectionProp = selectionProp.replace(/%/, "");
724       selectionProp = selectionProp.replace(/\s+/g, "");
725     }
727     return {
728       data_type: dataType,
729       cluster_type: clusterType,
730       k_number: kNumber,
731       selection_proportion: selectionProp,
732     };
733   },
735   
736   displayClusterPopsTable: function (tableId, data) {
738     var table = jQuery(`#${tableId}`).DataTable({
739       'searching': true,
740       'ordering': true,
741       'processing': true,
742       'paging': true,
743       'info': false,
744       'pageLength': 5,
745       'rowId': function (a) {
746         return a[7]
747       }
748     });
750     table.rows.add(data).draw();
752   },
755   getClusterPopsRows: function(clusterPops) {
757     var clusterPopsRows = [];
759     for (var i = 0; i < clusterPops.length; i++) {
760       if (clusterPops[i]) {
761         var clusterPopRow = this.createRowElements(clusterPops[i]);
762         clusterPopsRows.push(clusterPopRow);
763       }
764     }
766     return clusterPopsRows;
768   },
770   getClusterPops: function () {
771     var list = new solGSList();
772     var lists = list.getLists(["accessions", "plots", "trials"]);
773     lists = list.addDataStrAttr(lists);
774     lists = list.addDataTypeAttr(lists);
776     var datasets = solGS.dataset.getDatasetPops(["accessions", "trials"]);
777     datasets = solGS.dataset.addDataTypeAttr(datasets);
778     clusterPops = [lists, datasets];
780     return clusterPops.flat();
782   },
785   getSelectedPopClusterArgs: function (runClusterElemId) {
786     var clusterArgs;
788     var selectedPopDiv = document.getElementById(runClusterElemId);
789     if (selectedPopDiv) {
790       var selectedPopData = selectedPopDiv.dataset;
792       clusterArgs = JSON.parse(selectedPopData.selectedPop);
793       var clusterPopId = clusterArgs.data_str + "_" + clusterArgs.id;
795       var protocolId = solGS.genotypingProtocol.getGenotypingProtocolId("cluster_div");
797       clusterArgs["analysis_type"] = "cluster analysis";
798       clusterArgs["genotyping_protocol_id"] = protocolId;
799       clusterArgs["cluster_pop_id"] = clusterPopId;
800       clusterArgs["data_structure"] = clusterArgs.data_str;
801     }
803     return clusterArgs;
804   },
806   populateClusterMenu: function (newPop) {
807     var modelData = solGS.selectMenuModelArgs();
808     var clusterPops = [modelData];
810     if (modelData.id.match(/list/) == null) {
811       var trialSelPopsList = solGS.selectionPopulation.getPredictedTrialTypeSelectionPops();
812       if (trialSelPopsList) {
813         clusterPops.push(trialSelPopsList);
814       }
815     }
816   
817     var menu = new SelectMenu(this.clusterPopsDiv, this.clusterPopsSelectMenuId);
819     if (newPop){
820         menu.updateOptions(newPop);   
821     } else {
822       menu.populateMenu(clusterPops);
823     }
825   },
827   cleanupFeedback: function(canvasId, runBtnId, msgDiv, message) {
828     jQuery(`${canvasId} .multi-spinner-container`).hide();
829     jQuery(runBtnId).show();
831     jQuery(msgDiv).empty();
833     if (message) {
834       jQuery(msgDiv).html(message);
835     }
837   },
839   plotCluster: function (plotData, downloadLinks) {
840     var scores = plotData.pc_scores_groups;
842     var pc12 = [];
843     var pc1 = [];
844     var pc2 = [];
845     var groups = [];
847     jQuery.each(scores, function (i, pc) {
848       pc12.push([
849         {
850           name: pc[0],
851           group: pc[1],
852           pc1: parseFloat(pc[2]),
853           pc2: parseFloat(pc[3]),
854         },
855       ]);
857       pc1.push(parseFloat(pc[2]));
858       pc2.push(parseFloat(pc[3]));
860       if (!groups.includes(pc[1])) {
861         groups.push(pc[1]);
862       }
863     });
865     var groupsNames = groups;
867     var height = 400;
868     var width = 400;
869     var pad = {
870       left: 60,
871       top: 20,
872       right: 40,
873       bottom: 20,
874     };
876     var totalH = height + pad.top + pad.bottom + 100;
877     var totalW = width + pad.left + pad.right + 400;
879     var clusterCanvasDivId = this.canvas;
880     var clusterPlotPopId = plotData.file_id;
881     var clusterPlotDivId = `${this.clusterPlotDiv}_${clusterPlotPopId}`;
882     clusterPlotDivId = clusterPlotDivId.replace(/#/, "");
884     jQuery(clusterCanvasDivId).append("<div id=" + clusterPlotDivId + "></div>");
885     clusterPlotDivId = "#" + clusterPlotDivId;
887     var svg = d3
888       .select(clusterPlotDivId)
889       .insert("svg", ":first-child")
890       .attr("width", totalW)
891       .attr("height", totalH);
893     var clusterPlot = svg.append("g")
894       .attr("id", clusterPlotDivId)
895       .attr("transform", "translate(0,0)");
897     var pc1Min = d3.min(pc1);
898     var pc1Max = d3.max(pc1);
900     var pc1Limits = d3.max([Math.abs(d3.min(pc1)), d3.max(pc1)]);
901     var pc2Limits = d3.max([Math.abs(d3.min(pc2)), d3.max(pc2)]);
902    
903     var pc1AxisScale = d3.scaleLinear().domain([0, pc1Limits]).range([0, width / 2]);
904     var pc2AxisScale = d3.scaleLinear().domain([0, pc2Limits]).range([0, height / 2]);
906     var pc1AxisLabel = d3.scaleLinear().domain([-1 * pc1Limits, pc1Limits]).range([0, width]);
907     var pc2AxisLabel = d3.scaleLinear().domain([-1 * pc2Limits, pc2Limits]).range([height, 0]);
909     var pc1Axis = d3.axisBottom(pc1AxisLabel).tickSize(3);
910     var pc2Axis = d3.axisLeft(pc2AxisLabel).tickSize(3);
912     var nudgeVal = 15;
913     var axesLabelColor = "green";
914     var labelFs = 12;
915     var yAxisHeight = pad.top + height + nudgeVal; 
917     clusterPlot
918       .append("g")
919       .attr("class", "PC1 axis")
920       .attr("transform", "translate(" + pad.left + "," + yAxisHeight + ")")
921       .call(pc1Axis)
922       .selectAll("text")
923       .attr("y", 0)
924       .attr("x", 15)
925       .attr("dy", ".1em")
926       .attr("transform", "rotate(90)")
927       .attr("fill", axesLabelColor)
928       .style("text-anchor", "start")
929       .style("fill", axesLabelColor);
931     clusterPlot
932       .append("g")
933       .attr("class", "PC2 axis")
934       .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
935       .call(pc2Axis)
936       .selectAll("text")
937       .attr("y", 0)
938       .attr("x", -10)
939       .style("fill", axesLabelColor);
941     var grpColor = d3.scaleOrdinal(d3.schemeCategory10);
943     clusterPlot.append("g")
944       .selectAll("circle")
945       .data(pc12)
946       .enter()
947       .append("circle")
948       .style("fill", function (d) {
949         return grpColor(groups.indexOf(d[0].group));
950       })
951       .attr("r", 3)
952       .attr("cx", function (d) {
953         var xVal = d[0].pc1;
954         if (xVal >= 0) {
955           return pad.left + width / 2 + pc1AxisScale(xVal);
956         } else {
957           return pad.left + width / 2 - -1 * pc1AxisScale(xVal);
958         }
959       })
960       .attr("cy", function (d) {
961         var yVal = d[0].pc2;
962         if (yVal >= 0) {
963           return pad.top + height / 2 - pc2AxisScale(yVal);
964         } else {
965           return pad.top + height / 2 + -1 * pc2AxisScale(yVal);
966         }
967       })
968       .on("mouseover", function (d) {
969         d3.select(this).attr("r", 5).style("fill", axesLabelColor);
970         clusterPlot
971         .data(d)
972           .append("text")
973           .attr("id", "dLabel")
974           .style("fill", function(d){ 
975             return grpColor(groups.indexOf(d.group)); })
976           .text(d[0].name + "(" + d[0].pc1 + "," + d[0].pc2 + ")")
977           .attr("x", width + pad.left + 2 * nudgeVal)
978           .attr("y", height - 0.1 * height);
979       })
980       .on("mouseout", function (d) {
981         d3.select(this)
982           .attr("r", 3)
983           .style("fill", function (d) {
984             return grpColor(groups.indexOf(d[0].group));
985           });
986         d3.selectAll("text#dLabel").remove();
987       });
989     clusterPlot
990       .append("rect")
991       .attr("transform", "translate(" + pad.left + "," + pad.top + ")")
992       .attr("height", height + nudgeVal)
993       .attr("width", width + nudgeVal)
994       .attr("fill", "none")
995       .attr("stroke", "#523CB5")
996       .attr("stroke-width", 1)
997       .attr("pointer-events", "none");
999     var popName = "";
1000     if (plotData.list_name) {
1001       popName = plotData.list_name;
1002     }
1004     if (downloadLinks) {
1005       jQuery(clusterPlotDivId).append('<p style="margin-left: 40px">' + downloadLinks + "</p>");
1006     }
1008     if (groupsNames && Object.keys(groupsNames).length > 1) {
1009       var recLH = 20;
1010       var recLW = 20;
1011       var legendXOrig = pad.left + 2 * nudgeVal + width;
1012       var legendYOrig = height * 0.25;
1013       var legendValues = groups;
1015       clusterPlot
1016       .append("g")
1017       .attr("class", "cell")
1018       .attr("transform", "translate(" + legendXOrig + "," + legendYOrig + ")")
1019       .append("text")
1020       .text("Clusters")
1021       .attr("x", 0)
1022       .attr("y", 15)
1023       .style("font-size",'12px')
1024       .style("fill", axesLabelColor);
1026      clusterPlot
1027         .append("g")
1028         .attr("class", "cell")
1029         .attr("transform", "translate(" + legendXOrig + "," + legendYOrig + ")")
1030         .attr("height", 100)
1031         .attr("width", 100)
1032         .selectAll("rect")
1033         .data(legendValues)
1034         .enter()
1035         .append("rect")
1036         .attr("x", function (d) {
1037           return 1;
1038         })
1039         .attr("y", function (d) {
1040           return 1 + d * recLH + d * 5;
1041         })
1042         .attr("width", recLH)
1043         .attr("height", recLW)
1044         .style("stroke", "black")
1045         .style("fill", function (d) {
1046           return grpColor(groups.indexOf(d));
1047         });
1049    clusterPlot
1050         .append("g")
1051         .attr(
1052           "transform",
1053           "translate(" + (legendXOrig + 30) + "," + (legendYOrig + 0.5 * recLW) + ")"
1054         )
1055         .attr("id", "legendtext")
1056         .selectAll("text")
1057         .data(legendValues)
1058         .enter()
1059         .append("text")
1060         .attr("fill", "#523CB5")
1061         .style("fill", "#523CB5")
1062         .attr("x", 1)
1063         .attr("y", function (d) {
1064           return 1 + d[0] * recLH + d[0] * 5;
1065         })
1066         .text(function (d) {
1067           return d;
1068         })
1069         .attr("dominant-baseline", "middle")
1070         .attr("text-anchor", "start");
1071     }
1073   },
1076 jQuery.fn.doesExist = function () {
1077   return jQuery(this).length > 0;
1080 jQuery(document).ready(function () {
1081   var url = location.pathname;
1083   if (url.match(/cluster\/analysis/)) {
1084     var canvas = solGS.cluster.canvas;
1085     var clusterMsgDiv = solGS.cluster.clusterMsgDiv;
1087     var clusterArgs = solGS.cluster.getClusterArgsFromUrl();
1088     var clusterPopId = clusterArgs.cluster_pop_id;
1090     if (clusterPopId) {
1091       jQuery(clusterMsgDiv).text("Running cluster... please wait...it may take minutes.").show();
1092       jQuery(`${canvas} .multi-spinner-container`).show();
1094       solGS.cluster.checkCachedCluster(url, clusterArgs).done(function (res) {
1095         if (res.result == "success") {
1096           solGS.cluster.displayClusterOutput(res);
1098           jQuery(clusterMsgDiv).empty();
1099           jQuery(`${canvas} .multi-spinner-container`).hide();
1100         }
1101       });
1102     }
1103   }
1107 jQuery(document).ready(function () {
1108   var canvas = solGS.cluster.canvas;
1110   jQuery(canvas).on("click", "a", function (e) {
1111     var linkId = e.target.id;
1112     var clusterPlotId = linkId.replace(/download_/, "");
1114     if (clusterPlotId.match(/cluster_plot_/)) {
1115       saveSvgAsPng(document.getElementById(`#${clusterPlotId}`), `${clusterPlotId}.png`, { scale: 2});
1116     }
1117   });
1121 jQuery(document).ready(function () {
1122   jQuery("#cluster_div").on("change", "#cluster_type_opts", function () {
1123     var rowId = jQuery(this).closest("tr").attr("id");
1125     var clusterTypeId = solGS.cluster.clusterTypeSelectId(rowId);
1126     var kNumId = solGS.cluster.clusterKnumSelectId(rowId);
1127     var clusterType = jQuery("#" + clusterTypeId).val();
1128     if (clusterType.match(/hierarchical/i)) {
1129       jQuery("#k_number_div").hide();
1130       jQuery("#" + kNumId).prop("disabled", true);
1131     } else {
1132       jQuery("#k_number_div").show();
1133       jQuery("#" + kNumId).prop("disabled", false);
1134     }
1135   });
1138 jQuery(document).ready(function () {
1140   jQuery("#cluster_div").on("change", "#cluster_selected_pop", function () {
1141     var rowId = jQuery(this).closest("tr").attr("id");
1143     var popType = jQuery("#cluster_selected_pop_type").val();
1144     var clusterTypeId = solGS.cluster.clusterTypeSelectId(rowId);
1146     var clusterDataType = jQuery("#" + clusterDataTypeId).val();
1147   });
1150 jQuery(document).ready(function () {
1151   jQuery("#cluster_div").on("click", function (e) {
1152     var runClusterBtnId = e.target.id;
1153     if (runClusterBtnId.match(/run_cluster/)) {
1154       jQuery(clusterMsgDiv).text("Running cluster... please wait...it may take minutes.").show();
1156       jQuery(`${canvas} .multi-spinner-container`).show();
1158       var canvas = solGS.cluster.canvas;
1159       var clusterMsgDiv = solGS.cluster.clusterMsgDiv;
1160       var runClusterBtnId;
1161       var popType;
1162       var page = location.pathname;
1163       var selectedId, selectedName, dataStr;
1164       if (page.match(/cluster\/analysis/)) {
1165         var clusterArgs = solGS.cluster.getSelectedPopClusterArgs(runClusterBtnId);
1166         selectedId = clusterArgs.id;
1167         selectedName = clusterArgs.name;
1168         dataStr = clusterArgs.data_str;
1170       } else if (page.match(/breeders\/trial\//)) {
1171         selectedId = jQuery("#trial_id").val();
1172         selectedName = jQuery("#trial_name").val();
1173       } else {
1174         selectedId = jQuery("#cluster_selected_pop_id").val();
1175         selectedName = jQuery("#cluster_selected_pop_name").val();
1176         var popType = jQuery("#cluster_selected_pop_type").val();
1178         if (popType) {
1179           if (popType.match(/list/)) {
1180             dataStr = "list";
1181           } else if (popType.match(/dataset/)) {
1182             dataStr = "dataset";
1183           }
1184         }
1185       }
1187       var clusterOptsId = "cluster_options";
1188       var clusterPopId = solGS.cluster.getClusterPopId(selectedId, dataStr);
1189       var clusterOpts = solGS.cluster.clusteringOptions(clusterPopId);
1191       var clusterArgs = {
1192         selected_id: selectedId,
1193         selected_name: selectedName,
1194         data_structure: dataStr,
1195         cluster_pop_id: clusterPopId,
1196         cluster_type: clusterOpts.cluster_type,
1197         data_type: clusterOpts.data_type,
1198         k_number: clusterOpts.k_number,
1199         selection_proportion: clusterOpts.selection_proportion,
1200       };
1202       clusterArgs = solGS.cluster.clusterResult(clusterArgs);
1203       runClusterBtnId = solGS.cluster.getRunClusterBtnId(clusterPopId);
1204       var page = clusterArgs.analysis_page;
1206       runClusterBtnId = `#${runClusterBtnId}`;
1207       solGS.cluster
1208         .checkCachedCluster(page, clusterArgs)
1209         .done(function (res) {
1210           if (res.result == "success") {
1211             solGS.cluster.cleanupFeedback(canvas, runClusterBtnId, clusterMsgDiv);
1212             solGS.cluster.displayClusterOutput(res);
1213           } else {
1214             jQuery(`${canvas} .multi-spinner-container`).hide();
1215             jQuery(clusterMsgDiv).empty();
1216             var title =
1217               "<p>This analysis may take a long time. " +
1218               "Do you want to submit the analysis and get an email when it completes?</p>";
1220             var jobSubmit = '<div id= "cluster_submit">' + title + "</div>";
1222             jQuery(jobSubmit).appendTo("body");
1224             jQuery("#cluster_submit").dialog({
1225               height: "auto",
1226               width: "auto",
1227               modal: true,
1228               title: "cluster job submission",
1229               buttons: {
1230                 OK: {
1231                   text: "Yes",
1232                   class: "btn btn-success",
1233                   id: "queue_job",
1234                   click: function () {
1235                     jQuery(this).dialog("close");
1236                     solGS.submitJob.checkUserLogin(page, clusterArgs);
1237                   },
1238                 },
1240                 No: {
1241                   text: "No, I will wait till it completes.",
1242                   class: "btn btn-warning",
1243                   id: "no_queue",
1244                   click: function () {
1245                     jQuery(this).dialog("close");
1247                     jQuery(runClusterBtnId).hide();
1248                     jQuery(clusterMsgDiv)
1249                       .text("Running cluster... please wait...it may take minutes.")
1250                       .show();
1251                     jQuery(`${canvas} .multi-spinner-container`).show();
1253                     solGS.cluster
1254                       .runClusterAnalysis(clusterArgs)
1255                       .done(function (res) {
1256                         if (res.result == "success") {
1257                           if (res.pc_scores_groups) {
1258                             solGS.cluster.cleanupFeedback(canvas, runClusterBtnId, clusterMsgDiv )
1259                             solGS.cluster.displayClusterOutput(res);
1260                           } else {
1261                             var msg = "There is no cluster groups data to plot. " + 
1262                             "The R clustering script did not write cluster results to output files.";
1264                             solGS.cluster.cleanupFeedback(canvas, runClusterBtnId, clusterMsgDiv, msg);
1265                           }
1266                         } else {
1267                           var msg = "Error occured running the clustering. Possibly the R script failed.";
1268                           solGS.cluster.cleanupFeedback(canvas, runClusterBtnId, clusterMsgDiv, msg);
1269                         }
1270                       })
1271                       .fail(function () {
1272                         var msg = "Error occured running the clustering";
1273                         solGS.cluster.cleanupFeedback(canvas, runClusterBtnId, clusterMsgDiv, msg);
1274                       });
1275                   },
1276                 },
1278                 Cancel: {
1279                   text: "Cancel",
1280                   class: "btn btn-info",
1281                   id: "cancel_queue_info",
1282                   click: function () {
1283                     jQuery(this).dialog("close");
1284                     jQuery(runClusterBtnId).show();
1285                   },
1286                 },
1287               },
1288             });
1289           }
1290         })
1291         .fail(function () { });
1292     }
1293   });
1296 jQuery(document).ready(function () {
1297   var page = location.pathname;
1299   if (page.match(/solgs\/traits\/all\/|solgs\/models\/combined\/trials\//)) {
1300     setTimeout(function () {
1301       solGS.cluster.populateClusterMenu();
1302     }, 5000);
1304     var dataTypeOpts = solGS.cluster.getDataTypeOpts();
1306     dataTypeOpts = solGS.cluster.createDataTypeSelect(dataTypeOpts);
1307     var clusterTypeOpts = solGS.cluster.createClusterTypeSelect();
1309     jQuery(document).ready(checkClusterPop);
1311     function checkClusterPop() {
1312       if (jQuery("#cluster_div #cluster_pops_select_div").is(":visible")) {
1313         jQuery("#cluster_div #cluster_options #cluster_data_type_opts").html(dataTypeOpts);
1314         jQuery("#cluster_div #cluster_options #cluster_type_opts").html(clusterTypeOpts);
1315         jQuery("#cluster_div #cluster_options").show();
1316       } else {
1317         setTimeout(checkClusterPop, 6000);
1318       }
1319     }
1320   } else {
1321     if (!page.match(/cluster\/analysis/)) {
1322       var dataTypeOpts = solGS.cluster.getDataTypeOpts();
1323       dataTypeOpts = solGS.cluster.createDataTypeSelect(dataTypeOpts);
1324       var clusterTypeOpts = solGS.cluster.createClusterTypeSelect();
1326       jQuery("#cluster_div #cluster_options #cluster_data_type_opts").html(dataTypeOpts);
1327       jQuery("#cluster_div #cluster_options #cluster_type_opts").html(clusterTypeOpts);
1328       jQuery("#cluster_div #cluster_options").show();
1329     }
1330   }
1333 jQuery(document).ready(function () {
1335   if (!location.pathname.match(/cluster\/analysis/)) {
1336     var clusterPopsDiv = solGS.cluster.clusterPopsDiv;
1338     jQuery(clusterPopsDiv).on("change", function () {
1339       var selectedPop = jQuery("option:selected", this).data("pop");
1341       var selectedPopId = selectedPop.id;
1342       var selectedPopName = selectedPop.name;
1343       var selectedPopType = selectedPop.type || selectedPop.pop_type;
1345       var dataTypeId = solGS.cluster.clusterDataTypeSelectId(selectedPopId);
1346       var dataType = jQuery("#" + dataTypeId).val();
1348       jQuery("#cluster_selected_pop_name").val(selectedPopName);
1349       jQuery("#cluster_selected_pop_id").val(selectedPopId);
1350       jQuery("#cluster_selected_pop_type").val(selectedPopType);
1352       var dataTypeOpts = solGS.cluster.getDataTypeOpts(selectedPop);
1354       dataTypeOpts = solGS.cluster.createDataTypeSelect(dataTypeOpts, selectedPopId);
1355       jQuery("#cluster_div #cluster_options #cluster_data_type_opts").html(dataTypeOpts);
1357       if (selectedPopType.match(/selection_index/) && dataType.match(/Genotype/i)) {
1358         jQuery("#cluster_div #cluster_options #selection_proportion_div").show();
1359       } else {
1360         jQuery("#cluster_div #cluster_options #selection_proportion_div").hide();
1361       }
1363     });
1364   }
1368 jQuery(document).ready(function () {
1369   if (location.pathname.match(/cluster\/analysis/)) {
1371     clusterPopsDataDiv = solGS.cluster.clusterPopsDataDiv;
1372     var tableId = 'cluster_pops_table';
1373     var clusterPopsTable = solGS.cluster.createTable(tableId)
1374     jQuery(clusterPopsDataDiv).append(clusterPopsTable).show();
1376     var clusterPops = solGS.cluster.getClusterPops()
1377     var clusterPopsRows = solGS.cluster.getClusterPopsRows(clusterPops);
1379     solGS.cluster.displayClusterPopsTable(tableId, clusterPopsRows)
1381     jQuery("#add_new_pops").show();
1382     
1383   }