refresh matview stockprop happens in ajax controller
[sgn.git] / js / CXGN / SelectionIndex.js
blob7e4c4c830b206236d07d701960cec9c02eac59b9
1 jQuery(document).ready(function() {
3     jQuery(document)
4         .on('show.bs.collapse', '.panel-collapse', function() {
5             var $span = jQuery(this).parents('.panel').find('.panel-heading span.clickable');
6             $span.find('i').removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-up');
7         })
8         .on('hide.bs.collapse', '.panel-collapse', function() {
9             var $span = jQuery(this).parents('.panel').find('.panel-heading span.clickable');
10             $span.find('i').removeClass('glyphicon-chevron-up').addClass('glyphicon-chevron-down');
11         })
13     jQuery('#pagetitle_h3').append('&nbsp;<a id="selection_index_more_info" href="#"><span class="glyphicon glyphicon-info-sign"></span></a>');
15     jQuery('#select_trial_for_selection_index').change( // update selection index options when trial selection changes
16         function() {
18             jQuery('#selection_index').html("");
19             jQuery('#trait_table').html("");
20             jQuery('#weighted_values_div').html("");
21             jQuery('#raw_avgs_div').html("");
22             jQuery('#sin_formula_list_select').val("");
23             update_formula();
25             if (jQuery(this).val() == '') {
26                 return;
27             };
29             jQuery('#trait_table_label').html('Traits and coefficients for <a href="/breeders_toolbox/trial/'+jQuery(this).val()+'">'+jQuery('option:selected', this).text()+'</a>:')
31             var trial_id = jQuery(this).val();
32             var trial_name = jQuery("option:selected", this).text();
34             var data = [
35                 [jQuery(this).val()]
36             ];
38             jQuery.ajax({ // get traits phenotyped in trial
39                 url: '/ajax/breeder/search',
40                 method: 'POST',
41                 data: {
42                     'categories': ['trials', 'traits'],
43                     'data': data,
44                     'querytypes': 0
45                 },
46                 beforeSend: function() {
47                     disable_ui();
48                 },
49                 complete: function() {
50                     enable_ui();
51                 },
52                 success: function(response) {
53                     var list = response.list || 0;
54                     if (!list) {
55                       trait_html = '<option id="select_message" value="" title="No trait measurements found.">No trait measurements found for '+trial_name+'.</option>\n';
56                       jQuery('#trait_list').html(trait_html);
57                       return;
58                     }
59                     var trait_ids = [];
60                     for (i = 0; i < list.length; i++) {
61                         trait_ids.push(list[i][0]);
62                     }
64                     var synonyms;
65                     jQuery.ajax({ // get trait synonyms
66                         url: '/ajax/cvterm/get_synonyms',
67                         async: false,
68                         method: 'POST',
69                         data: {
70                             'trait_ids': trait_ids
71                         },
72                         success: function(response) {
73                             synonyms = response.synonyms;
74                             //console.log("synonyms = " + JSON.stringify(synonyms));
75                             trait_html = '<option id="select_message" value="" title="Select a trait">Select a trait</option>\n';
76                             for (i = 0; i < list.length; i++) {
77                                 var trait_id = list[i][0];
78                                 var trait_name = list[i][1];
79                                 var parts = trait_name.split("|");
80                                 var synonym = synonyms[trait_id];
81                             //    synonym_fixed = synonym.replace(/"/g, "");
82                             //    var syn_parts = synonym_fixed.split(" ");
83                               //  synonym_fixed = syn_parts[0];
84                                 trait_html += '<option value="' + trait_id + '" data-synonym="' + synonym + '" data-list_name="' + trait_name + '" title="' + parts[0] + '">' + parts[0] + '</a>\n';
85                             }
87                             jQuery('#trait_list').html(trait_html);
89                         },
90                         error: function(response) {
91                             alert("An error occurred while retrieving synonyms for traits with ids " + trait_ids);
92                         }
93                     });
94                 },
95                 error: function(response) {
96                     alert("An error occurred while transforming the list " + list_id);
97                 }
98             });
100             jQuery.ajax({ // get plots phenotyped in trial
101                 url: '/ajax/breeder/search',
102                 method: 'POST',
103                 data: {
104                     'categories': ['trials', 'plots'],
105                     'data': data,
106                     'querytypes': 0
107                 },
108                 success: function(response) {
109                     var plots = response.list || [];
110                     //console.log("plots: " + JSON.stringify(plots));
111                     var plot_ids = plots.map(function(val) {
112                         return val[0]
113                     });
114                     //console.log("plot ids: " + JSON.stringify(plot_ids));
115                     jQuery.ajax({
116                         url: '/ajax/breeders/trial/' + data + '/controls_by_plot',
117                         data: {
118                             'plot_ids': plot_ids
119                         },
120                         success: function(response) {
121                             //console.log('controls:' + JSON.stringify(response));
122                             var accessions = response.accessions;
123                             var accession_html;
124                             if (response.accessions[0].length == 0) {
125                                 accession_html = '<option value="" title="Select a control">No controls found</a>\n';
126                             } else {
127                                 accession_html = '<option value="" title="Select a control">Select a control</a>\n';
128                                 for (i = 0; i < response.accessions[0].length; i++) {
129                                     accession_html += '<option value="' + accessions[0][i].stock_id + '" title="' + response.accessions[0][i].stock_id + '">' + response.accessions[0][i].accession_name + '</a>\n';
130                                 }
131                             }
132                             jQuery('#control_list').html(accession_html);
133                             jQuery('#trait_list').focus();
134                         },
135                         error: function(response) {
136                             jQuery('#control_list').html('<option value="" title="Select a control">Error retrieving trial controls</a>');
137                         }
138                     });
139                 },
140                 error: function(response) {
141                     jQuery('#control_list').html('<option value="" title="Select a control">Error retrieving trial design</a>');
142                 }
143             });
144         });
146     jQuery('#trait_list').change( // add selected trait to trait table
147         function() {
148             var trait_id = jQuery('option:selected', this).val();
149             var coefficient_id = trait_id + '_coefficient';
150             var control_id = trait_id + '_control';
151             var trait_name = jQuery('option:selected', this).text();
152             var trait_synonym = jQuery('option:selected', this).data("synonym");
153             var list_name = jQuery('option:selected', this).data("list_name");
154             var control_html = jQuery('#control_list').html();
155             var trait_html = "<tr id='" + trait_id + "_row'><td><a href='/cvterm/" + trait_id + "/view' data-list_name='" + list_name + "' data-value='" + trait_id + "'>" + trait_name + "</a></td><td><p id='" + trait_id + "_synonym'>" + trait_synonym + "<p></td><td><input type='text' id='" + coefficient_id + "' class='form-control' placeholder='Default is 1'></input></td><td><select class='form-control' id='" + control_id + "'>" + control_html + "</select></td><td align='center'><a title='Remove' id='" + trait_id + "_remove' href='javascript:remove_trait(" + trait_id + ")'><span class='glyphicon glyphicon-remove'></span></a></td></tr>";
156             jQuery('#trait_table').append(trait_html);
157             jQuery('#select_message').text('Add another trait');
158             jQuery('#select_message').attr('selected', true);
159             update_formula();
160             jQuery('#' + coefficient_id).focus();
161             jQuery('#' + coefficient_id).change( //
162                 function() {
163                   if (isNaN(jQuery(this).val())) {
164                     jQuery(this).val('');
165                     document.getElementById('selection_index_error_message').innerHTML = "<center><li class='list-group-item list-group-item-danger'> Error.<br> Index coefficients must be a positive or negative number.</li></center>";
166                     jQuery('#selection_index_error_dialog').modal("show");
167                   }
168                   else {
169                     update_formula();
170                     jQuery('#trait_list').focus();
171                   }
172                 });
173             jQuery('#' + control_id).change(
174                 function() {
175                     update_formula();
176                 });
177             //jQuery('#calculate_rankings').removeClass('disabled');
178         });
180     jQuery('#save_sin').click(function() {
181       if (jQuery('#trait_table').children().length < 1) {
182         document.getElementById('selection_index_error_message').innerHTML = "<center><li class='list-group-item list-group-item-danger'> Formula not saved.<br> At least one trait must be selected before saving a SIN formula.</li></center>";
183         jQuery('#selection_index_error_dialog').modal("show");
184         return;
185       }
186         var lo = new CXGN.List();
187         var new_name = jQuery('#save_sin_name').val();
188         console.log("Saving SIN formula to list named " + new_name);
189         var selected_trait_rows = jQuery('#trait_table').children();
190         var trait_ids = [],
191             traits = [],
192             coefficients = [],
193             controls = [];
194         jQuery(selected_trait_rows).each(function(index, selected_trait_rows) {
195             var trait_id = jQuery('a', this).data("value");
196             traits.push(jQuery('a', this).data("list_name"));
197             coefficients.push(jQuery('#' + trait_id + '_coefficient').val() || 1); // default = 1
198             var control_name;
199             if (jQuery('#' + trait_id + '_control option:selected').val()) {
200               control_name = jQuery('#' + trait_id + '_control option:selected').text();
201             }
202             else {
203               control_name = '';
204             }
205             controls.push(control_name.trim());
206         });
208         var data = "traits:" + traits.join();
209         data += "\nnumbers:" + coefficients.join();
210         data += "\naccessions:" + controls.join();
211         console.log("Saving SIN formula to dataset: " + JSON.stringify(data));
213         list_id = lo.newList(new_name);
214         if (list_id > 0) {
215             var elementsAdded = lo.addToList(list_id, data);
216             lo.setListType(list_id, 'dataset');
217         }
218         if (elementsAdded) {
219             alert("Saved SIN formula with name " + new_name);
220         }
222     });
224     jQuery('#selection_index_more_info').click(function() {
225       jQuery('#selection_index_info_dialog').modal("show");
226     });
228     jQuery('#selection_index_error_close_button').click(function() {
229         document.getElementById('selection_index_error_message').innerHTML = "";
230     });
233     jQuery('#calculate_rankings').click(function() {
235       if (jQuery('#trait_table').children().length < 1) {
236         document.getElementById('selection_index_error_message').innerHTML = "<center><li class='list-group-item list-group-item-danger'> Error.<br> A trial and at least one trait must be selected before calculating rankings.</li></center>";
237         jQuery('#selection_index_error_dialog').modal("show");
238         return;
239       }
240         jQuery('#raw_avgs_div').html("");
241         jQuery('#weighted_values_div').html("");
242         var trial_id = jQuery("#select_trial_for_selection_index option:selected").val();
243         var selected_trait_rows = jQuery('#trait_table').children();
244         var trait_ids = [],
245             column_names = [],
246             weighted_column_names = [],
247             coefficients = [],
248             controls = [];
250         var trial_name = jQuery('#select_trial_for_selection_index option:selected').text();
251         column_names.push({
252             title: "Accession"
253         });
254         weighted_column_names.push({
255             title: "Accession"
256         });
257         jQuery(selected_trait_rows).each(function(index, selected_trait_rows) {
258             var trait_id = jQuery('a', this).data("value");
259             trait_ids.push(trait_id);
260             var trait = jQuery('#' + trait_id + '_synonym').text()
261             if (trait == 'None') {
262               trait = jQuery('a', this).text();
263             }
264             var trait_term = trait; //= "mean "+trait;
265             var coefficient = jQuery('#' + trait_id + '_coefficient').val() || 1; // default = 1
268             coefficients.push(coefficient);
269             var control = jQuery('#' + trait_id + '_control option:selected').val() || '';
270             controls.push(control);
271             if (control) {
272                 trait_term += " as a fraction of " + jQuery('#' + trait_id + '_control option:selected').text();
273             }
274             column_names.push({
275                 title: trait_term
276             });
277             weighted_column_names.push({
278                 title: coefficient + " * (" + trait_term + ")"
279             });
280         });
281         weighted_column_names.push({
282             title: "SIN"
283         }, {
284             title: "SIN Rank"
285         });
286         var allow_missing = jQuery("#allow_missing").is(':checked');
288         console.log("trait_ids:" + trait_ids + "\ncoefficients:" + coefficients + "\ncontrols:" + controls);
290         jQuery.ajax({ // get raw averaged and weighted phenotypes from trial
291             url: '/ajax/breeder/search/avg_phenotypes',
292             method: 'POST',
293             data: {
294                 'trial_id': trial_id,
295                 'trait_ids': trait_ids,
296                 'coefficients': coefficients,
297                 'controls': controls,
298                 'allow_missing': allow_missing
299             },
300             success: function(response) {
301                 if (response.error) {
302                     alert(response.error);
303                 }
304                 var raw_avgs = response.raw_avg_values || [];
305                 var weighted_values = response.weighted_values || [];
306                 var trial_name = jQuery('#select_trial_for_selection_index option:selected').text();
307                 build_table(raw_avgs, column_names, trial_name, 'raw_avgs_div');
308                 build_table(weighted_values, weighted_column_names, trial_name, 'weighted_values_div');
309             },
310             error: function(response) {
311                 alert("An error occurred while retrieving average phenotypes");
312             }
313         });
315     });
318 function build_table(data, column_names, trial_name, target_div) {
320     var table_id = target_div.replace("div", "table");
321     var table_type = target_div.replace("_div", "");
322     var table_html = '<br><br><div class="table-responsive" style="margin-top: 10px;"><table id="' + table_id + '" class="table table-hover table-striped table-bordered" width="100%"><caption class="well well-sm" style="caption-side: bottom;margin-top: 10px;"><center> Table description: <i>' + table_type + ' for trial ' + trial_name + '.</i></center></caption></table></div>'
323     if (table_type == 'weighted_values') {
324         table_html += '<div class="col-sm-12 col-md-12 col-lg-12"><hr><label>Save top ranked accessions to a list: </label><br><br><div class="col-sm-3 col-md-3 col-lg-3" style="display: inline-block"><label>By number:</label>&nbsp;<select class="form-control" id="top_number"></select></div><div class="col-sm-3 col-md-3 col-lg-3" style="display: inline-block"><label>Or percent:</label>&nbsp;<select class="form-control" id="top_percent"></select></div><div class="col-sm-6 col-md-6 col-lg-6"><div style="text-align:right" id="ranking_to_list_menu"></div><div id="top_ranked_names" style="display: none;"></div></div><br><br><br><br><br></div>';
325     }
327     jQuery('#' + target_div).html(table_html);
329     var export_message = 'Accession rankings calculated using a selection index at ' + window.location.href;
330     var penultimate_column = column_names.length - 2;
332     jQuery('#' + table_id).DataTable({
333         dom: 'Bfrtip',
334         buttons: [ 'copy',
335             {
336                 extend: 'excelHtml5',
337                 title: trial_name +'_rankings'
338             },
339             {
340                 extend: 'csvHtml5',
341                 title: trial_name +'_rankings'
342             },
343             {
344                 extend: 'pdfHtml5',
345                 title: trial_name +'_rankings',
346                 message: export_message
347             },
348             {
349                 extend: 'print',
350                 message: export_message
351             }
352         ],
353         data: data,
354         destroy: true,
355         paging: true,
356         order: [
357             [1, 'asc']
358         ],
359         lengthMenu: [
360             [10, 25, 50, -1],
361             [10, 25, 50, "All"]
362         ],
363         columns: column_names,
364         order: [
365             [penultimate_column, "desc"]
366         ],
367     });
369     if (table_type == 'weighted_values') {
371         var table = $('#weighted_values_table').DataTable();
372         var name_links = table.column(0).data();
373         jQuery("#top_number").append('<option value="">Select a number</option>');
374         jQuery("#top_percent").append('<option value="">Select a percent</option>');
376         for (i = 1; i <= name_links.length; i++) {
377             jQuery("#top_number").append('<option value=' + i + '>' + i + '</option>');
378         }
379         for (i = 1; i <= 100; i++) {
380             jQuery("#top_percent").append('<option value=' + i + '>' + i + '%</option>');
381         }
383         jQuery('select[id^="top_"]').change( // save top # or % of accession to add to lists
384             function() {
385                 var type = this.id.split("_").pop();
386                 var number = jQuery('#top_' + type).val();
387                 var names = [];
389                 if (type == 'number') {
390                     jQuery("#top_percent").val(''); // reset other select
391                     for (var i = 0; i < number; i++) { //extract names from anchor tags
392                         names.push(name_links[i].match(/<a [^>]+>([^<]+)<\/a>/)[1] + '\n');
393                     }
394                     //console.log("retrieved top "+number+" names: "+names);
395                 } else if (type == 'percent') {
396                     jQuery("#top_number").val(''); // reset other select
397                     var adjusted_number = Math.round((number / 100) * name_links.length);
398                     for (var i = 0; i < adjusted_number; i++) { //extract names from anchor tags
399                         names.push(name_links[i].match(/<a [^>]+>([^<]+)<\/a>/)[1] + '\n');
400                     }
401                     //console.log("retrieved top "+number+" percent of names: "+names);
402                 }
403                 jQuery('#top_ranked_names').html(names);
404                 addToListMenu('ranking_to_list_menu', 'top_ranked_names', {
405                     listType: 'accessions',
406                 });
407             });
409     }
412 function load_sin() { // update traits and selection index when a saved sin formula is picked
414     if (!jQuery("#select_trial_for_selection_index option:selected").val()) {
415       document.getElementById('selection_index_error_message').innerHTML = "<center><li class='list-group-item list-group-item-danger'> Error.<br> A trial must be selected before loading a SIN formula.</li></center>";
416       jQuery('#selection_index_error_dialog').modal("show");
417       return;
418     }
420     //retrieve contents of list:
421     var sin_list_id = jQuery('#sin_formula_list_select').val();
422     if (!sin_list_id) {
423         update_formula();
424         return;
425     }
426     var lo = new CXGN.List();
427     var list_data = lo.getListData(sin_list_id);
428     var sin_data = list_data.elements;
430     var traits = [];
431     var coefficients = [];
432     var controls = [];
434     for (i = 0; i < sin_data.length; i++) {
435         var array = sin_data[i];
436         array.shift();
437         var string = array.shift();
438         var parts = string.split(/:(.+)/);
439         var values = parts[1];
440         switch (parts[0]) {
441             case 'traits':
442                 traits = values.split(",");
443                 break;
444             case 'numbers':
445                 coefficients = values.split(",");
446                 break;
447             case 'accessions':
448                 controls = values.split(",");
449                 break;
450         }
451     }
453     var ids = lo.transform(sin_list_id, 'dataset_2_dataset_ids');
454     var trait_ids = [];
455     var control_ids = [];
457     for (i = 0; i < ids.length; i++) {
458         var data = ids[i];
459         var parts = data.split(/:(.+)/);
460         var values = parts[1];
461         switch (parts[0]) {
462             case 'trait_ids':
463                 trait_ids = values.split(",");
464                 break;
465             case 'accession_ids':
466                 control_ids = values.split(",");
467                 break;
468         }
469     }
471     jQuery('#selection_index').html("");
472     jQuery('#trait_table').html("");
473     var omitted_traits = [];
474     var omitted_controls = [];
476     //add traits, coefficients, and controls to table
477     for (i = 0; i < trait_ids.length; i++) {
478         var trait_id = trait_ids[i];
479         var control_id = control_ids[i];
480         //console.log("building trait table with trait:" + trait_id + traits[i] + " and coefficient:" + coefficients[i] + " and control:" + control_id + controls[i]);
481         var coefficient_input_id = trait_id + '_coefficient';
482         var control_select_id = trait_id + '_control';
483         var trait_name = jQuery('#trait_list option[value=' + trait_id + ']').text();
484         if (!trait_name) {
485             omitted_traits.push("<a href='/cvterm/" + trait_id + "/view' data-value='" + trait_id + "'>" + traits[i] + "</a>");
486             continue;
487         }
488         var trait_synonym = jQuery('#trait_list option[value=' + trait_id + ']').data("synonym");
489         var control_html = jQuery('#control_list').html();
490         //console.log("control html"+control_html);
491         var trait_html = "<tr id='" + trait_id + "_row'><td><a href='/cvterm/" + trait_id + "/view' data-value='" + trait_id + "'>" + trait_name + "</a></td><td><p id='" + trait_id + "_synonym'>" + trait_synonym + "<p></td><td><input type='text' id='" + coefficient_input_id + "' class='form-control' placeholder='Default is 1'></input></td><td><select class='form-control' id='" + control_select_id + "'>" + control_html + "</select></td><td align='center'><a title='Remove' id='" + trait_id + "_remove' href='javascript:remove_trait(" + trait_id + ")'><span class='glyphicon glyphicon-remove'></span></a></td></tr>";
493         jQuery('#trait_table').append(trait_html);
494         jQuery('#' + coefficient_input_id).val(coefficients[i]);
495         if (jQuery('#' + control_select_id).find('option[value="' + control_id + '"]').length) {
496             jQuery('#' + control_select_id).val(control_id);
497         } else if (control_id) {
498             omitted_controls.push("<a href='/stock/" + control_id + "/view' data-value='" + control_id + "'>" + controls[i] + "</a>");
499         }
500     }
501     jQuery('#select_message').text('Add another trait');
502     jQuery('#select_message').attr('selected', true);
503     update_formula();
505     if (omitted_traits.length > 0 && omitted_controls.length > 0) {
506       document.getElementById('selection_index_error_message').innerHTML = "<center><li class='list-group-item list-group-item-danger'> The following parts of the saved SIN formula have been omitted because they were not found in this trial:</li></center><br><center><p>Traits: " + omitted_traits.join(", ") + "</p></center><br><center><p>Controls: " + omitted_controls.join(", ") + "</p></center>";
507       jQuery('#selection_index_error_dialog').modal("show");
508     }
509     else if (omitted_traits.length > 0) {
510       document.getElementById('selection_index_error_message').innerHTML = "<center><li class='list-group-item list-group-item-danger'> The following parts of the saved SIN formula have been omitted because they were not found in this trial:</li></center><br><center><p>Traits: " + omitted_traits.join(", ") + "</p></center>";
511       jQuery('#selection_index_error_dialog').modal("show");
512     }
513     else if (omitted_controls.length > 0) {
514       document.getElementById('selection_index_error_message').innerHTML = "<center><li class='list-group-item list-group-item-danger'> The following parts of the saved SIN formula have been omitted because they were not found in this trial:</li></center><br><center><p>Controls: " + omitted_controls.join(", ") + "</p></center>";
515       jQuery('#selection_index_error_dialog').modal("show");
516     }
520 function remove_trait(trait_id) {
521     jQuery('#' + trait_id + '_row').remove();
522     update_formula();
525 function update_formula() {
526     //console.log("updating formula....");
527     var selected_trait_rows = jQuery('#trait_table').children();
528     if (selected_trait_rows.length < 1) {
529         jQuery('#ranking_formula').html("<center><i>Select a trial, then pick traits and coefficients (or load a saved formula).</i></center>");
530         jQuery('#calculate_rankings').addClass('disabled');
531         jQuery('#save_sin').addClass('disabled');
532         return;
533     }
534     var formula = "<center><b>SIN = </b></center>";
535     var term_number = 0;
536     jQuery(selected_trait_rows).each(function(index, selected_trait_rows) {
537         var trait_id = jQuery('a', this).data("value");
538         var trait = jQuery('#' + trait_id + '_synonym').text()
539         if (trait == 'None') {
540           trait = jQuery('a', this).text();
541         }
542         var trait_term = trait; //= "mean "+trait;
543         var coefficient = jQuery('#' + trait_id + '_coefficient').val() || 1; // default = 1
544         if (jQuery('#' + trait_id + '_control option:selected').val()) { // if control selected for scaling
545             trait_term += " as a fraction of " + jQuery('#' + trait_id + '_control option:selected').text();
546         }
548         if (term_number == 0 || coefficient <= 0) {
549             formula += "<center>" + coefficient + " * ( " + trait_term + ")</center>";
550         } else {
551             formula += "<center>+ " + coefficient + " * ( " + trait_term + ")</center>";
552         }
553         term_number++;
554     });
555     jQuery('#ranking_formula').html(formula);
556     jQuery('#calculate_rankings').removeClass('disabled');
557     jQuery('#save_sin').removeClass('disabled');
558     return;