Merge pull request #5243 from solgenomics/topic/observations_upload_catch_error
[sgn.git] / mason / tools / stability / index.mas
blobb295cfda619e80f66a80083b44e1df94d0cd7c06
2 <%doc>
3 </%doc>
5 <%args>
7 </%args>
9 <%perl>
10   use JSON::Any;
13 </%perl>
16 <& '/util/import_javascript.mas', classes => ['jquery', 'jqueryui', 'CXGN.Login', 'd3.d3v4Min.js', 'CXGN.BreedersToolbox.HTMLSelect'] &>
19 <script src="https://cdn.jsdelivr.net/npm/vega@3.3.1"></script>
20 <script src="https://cdn.jsdelivr.net/npm/vega-lite@2"></script>
21 <script src="https://cdn.jsdelivr.net/npm/vega-embed@3"></script>
22 <script src="bower_components/radar-chart-d3/src/radar-chart.js"></script>
23 <script src="https://d3js.org/d3.v5.min.js"></script>
26 <& /page/page_title.mas, title=>"Stability" &>
29 <&| /page/info_section.mas, id=>"input_dataset_section", title=>"Select Input Data", collapsible=>1, collapsed=>0, subtitle=>'<a class="btn btn-link pull-right" href="/help/solgwas" target="_blank">Help <span class="glyphicon glyphicon-question-sign"></span></a>' &>
30   <input type="hidden" name="outformat" value="0" />
31   <div class="form-group form-inline">
32     <label for="dataset_select">Available Datasets: </label><div id="dataset_select"></div>
33     <br>
34     <button class="btn btn-primary" id="selectDataset" type="submit" name="selectDataset" value="selectDataset">Select Dataset</button>
35     <br>
36     </div>
37         <div class="form-group form-inline">
38           <label class="blast_select_label" for = pheno_select>Available&nbspTraits:&nbsp; </label>
39           <& /page/html_select.mas, name=>'pheno_select', id=>'pheno_select', params=>"class\=\"form-control input-sm blast_select_box\"", choices=>"" &>
40           <!-- <label class="blast_select_label" for="pheno_select">Available&nbspTraits:&nbsp; </label>
41         <& /page/html_select.mas, name=>'pheno_select', id=>'pheno_select', params=>"class\=\"form-control input-sm blast_select_box\"", choices=>"" &>
42         <button class="btn btn-primary" id="preview_trait_histogram_btn" type="submit" name="preview_trait_histogram_btn" value="preview_trait_histogram_btn">View Trait Histogram</button> -->
43         <br>
44         <br>
45         <br>
46         <center>
47           <div id="pheno_summary_table">
48           </div>
49         </center>
50         <br>
51         <br>
52         </div>
53       <div class = "form-group form-inline">
54        <label for = select_method>Available Methods: </label>
55        <select class="form-control input-sm" id="select_method">
56         <option selected value="method_empty"></option>
57         <option value="ammi">AMMI</option>
58         <option value="gge">GGE</option>
59        </select>
60        <div id = "select_method">
61       </div>
62     </div>
63       <div class = "form-group form-inline">
64        <label for = select_imput>Phenotype Impute: </label>
65        <select class="form-control input-sm" id="select_imput">
66         <option selected value="imput_empty"></option>
67         <option value="imput_yes">yes</option>
68         <option value="imput_no">no</option>
69        </select>
70        <div id = "select_method">
71       </div>
72       <br>
74             <div style="text-align: center">
75               <button class="btn btn-primary" id="runAMMI" type="submit" name="runAMMI" value="runAMMI">Run Stability</button>
76             </div>
77             <br />
78     <div id="tempfile" style="display:none" >
79     </div>
80 </&>
81 <&| /page/info_section.mas, title=>"Instructions", collapsible=>1, collapsed=>1, subtitle=>'<a id="download_table" class="download_tag" target="_blank" href="javascript:download_table();" title="Download results in tabular format">Table&#8675;</a>&nbsp;&nbsp;<a id="download_basic" class="download_tag" target="_blank" href="javascript:download();" title="Download results in basic format">Basic&#8675;</a>' &>
82 <center>
83     <div id="sgn_blast_graph" style="display:none">
84         <div id="myCanvas">
85           Your browser does not support the HTML5 canvas
86         </div>
87     </div>
88   </center>
89   <br>
90   <h5><b>Preparing Dataset:</b></h5> 
91   <h5> 1. Dataset must have at least trials and trait(s).</h5>
92   <h5> 2. Trials must be placed in more than 1 location. </h5>
93   <h5> 3. Accessions must be placed in all locations with replications. Check the trial design to make sure it has replications. </h5>
94   <h5> 4. The same trait must be available in all selected locations. Missing data is acceptable for some replications, however the model wont work if it is missing data in all replications. </h5>
95   <h5><b>Impuation:</b></h5>
96   <h5> Imputation is based on genotypes average using the R package mice. The imputation accuracy is processed by subsetting the data and calculating the correlation between imputed and original data. </h5>
97   <h5><b>Output:</b></h5>
98   <h5>The AMMI graphic shows stable varieties with horizintal lines. The slope is proportional to their stability index.</h5>
99   
100   <center>
101     <div id="stability_summary"></div>
102   </center>
103 </&>
104 <&| /page/info_section.mas, title=>"Output", collapsible=>1, collapsed=>0, subtitle=>'<a id="download_table" class="download_tag" target="_blank" href="javascript:download_table();" title="Download results in tabular format">Table&#8675;</a>&nbsp;&nbsp;<a id="download_basic" class="download_tag" target="_blank" href="javascript:download();" title="Download results in basic format">Basic&#8675;</a>' &>
105   <br>
106 <div id="stability_message_container">
107     <div id="stability_message"></div>
108 </div>
109 <br>
110     <center>
111     <div id="stability_output"></div>
112       <br>
113     <br>
114     <button class="btn btn-primary" id="download-button">Download CSV</button>
115   </center>
116 </&>
117   <&| /page/info_section.mas, title=>"Graphics", collapsible=>1, collapsed=>1, subtitle=>'<a id="download_graphics" class="download_tag" target="_blank" href="javascript:download_table();" title="Download results in tabular format">Table&#8675;</a>&nbsp;&nbsp;<a id="download_basic" class="download_tag" target="_blank" href="javascript:download();" title="Download results in basic format">Basic&#8675;</a>' &>
118   <div style="display: flex;">
119     <div>
120       <p>Press control (or cmd) to select multiple accessions.</p>
121       <select id="accession_select" multiple size="15" style="height: 200px;">
122         <!-- Options for accessions will be added dynamically -->
123       </select>
124     </div>
125     <div style="margin-left: 25px;">
126       <center>
127         <br>
128         <svg id="graphics_output" width="700" height="600"></svg>
129         <div id="tooltip"></div>
130       </center>
131     </div>
132   </div>
133 </&>
136 <script>
138 jQuery(document).ready(function() {
139   if (isLoggedIn()) {
140     get_select_box("datasets", "dataset_select", {"checkbox_name":"dataset_select_checkbox"});
141   }
142   else {
143     alert('You must be logged in to use Stability');
144   }
145   $('#pheno_select').attr("disabled",true).html('');
146   jQuery('#dataset_select').click(function() {
147     $('#pheno_select').attr("disabled",true).html('');
148     $('#pheno_summary_table').html('');
149     $('#stability_output').empty();
150   });
151   jQuery('#selectDataset').click(function() {
152     var dataset_id = get_dataset_id();
153     $.ajax({
154       url: '/ajax/stability/shared_phenotypes',
155       data: {'dataset_id': dataset_id},
156       success: function(response) {
157         if (response.error) {
158           $('#dataset_select').val('ERROR');
159         }
160         else {
161     var option_html = '<option selected="selected" value=""> </option>';
162       for (var i = 0; i < response.options.length; i++) {
163         option_html += '<option value="'+response.options[i][1]+'">'+(response.options[i][1])+'</option>';
164       }
165       $('#pheno_select').attr("disabled",false).html(option_html);
166         jQuery('#tempfile').html(response.tempfile);
167     }
168         },
169         error: function(response) {
170           alert("An error occurred, please check the datase.");
171       }
172     });
173   });
176 $(document).ready(function(){
177     // When the selection changes in the select_imput dropdown
178     $('#pheno_select').change(function(){
179         // Get the selected value
180         var tempfile = jQuery('#tempfile').html();
181         var pheno_select = $(this).val();
182         console.log("Selected value:", pheno_select);
183         console.log(tempfile); // Output the selected value to console
184         if (pheno_select !== '') {
185             $.get('/ajax/stability/getdata', { file: tempfile })
186                 .done(function(response) {
187                     if (response && Array.isArray(response.data)) {
188                         var data = response.data;
190                         // Process the data based on the selected column
191                         var selectedColumnData = data.map(function(item) {
192                             return item[pheno_select];
193                         });
195                         // Now you can further process the selectedColumnData array
196                         // console.log(selectedColumnData);
197                         var min = calculateMinimum(selectedColumnData);
198                         var max = calculateMaximum(selectedColumnData);
199                         var mean = calculateMean(selectedColumnData);
200                         var standardDeviation = calculateStandardDeviation(selectedColumnData);
201                         var missingPercentage = calculateMissingPercentage(selectedColumnData);
203                         // Generate and append the table HTML
204                         var tableHtml = '<table id="summaryTable">';
205                         tableHtml += '<thead class="bg-light-blue"><tr><th>Trait</th><th>Mean</th><th>Minimum</th><th>Maximum</th><th>Std Dev</th><th>Percent Missing</th></tr></thead>';
206                         tableHtml += '<tbody>';
207                         tableHtml += '<tr><td>' + pheno_select + '</td><td>' + mean + '</td><td>' + min + '</td><td>' + max + '</td><td>' + standardDeviation + '</td><td>' + missingPercentage + '</td></tr>';
208                         tableHtml += '</tbody>';
209                         tableHtml += '</table>';
210                         $('#pheno_summary_table').html(tableHtml);
211                     } else {
212                         console.error("Invalid response format.");
213                     }
214                 })
215                 .fail(function(jqXHR, textStatus, errorThrown) {
216                     console.error("Error fetching file:", errorThrown);
217                 });
218         } else {
219             // If pheno_select is empty, clear the table
220             $('#pheno_summary_table').html('');
221         }
222     });
225 function calculateMinimum(data) {
226     // Convert values to numbers and filter out NaN values from the data array
227     var filteredData = data.map(parseFloat).filter(function(value) {
228         return !isNaN(value);
229     });
231     // Calculate minimum from filtered data
232     var min = Math.min.apply(null, filteredData).toFixed(3);
233     return min;
238 function calculateMaximum(data) {
239     // Filter out NaN values from the data array
240     var filteredData = data.map(parseFloat).filter(function(value) {
241         return !isNaN(value);
242     });
244     // Calculate minimum from filtered data
245     var min = Math.max.apply(null, filteredData).toFixed(3);
246     return min;
250 function calculateMean(data) {
251   var filteredData = data.map(parseFloat).filter(function(value) {
252         return !isNaN(value);
253   });
254   var sum = 0;
255   var count = 0; // Initialize count for valid data points
256   for (var i = 0; i < filteredData.length; i++) {
257       // Parse each value to ensure it's treated as a number
258       var value = parseFloat(filteredData[i]);
259       // Check if the parsed value is a valid number
260       if (!isNaN(value)) {
261           sum += value;
262           count++; // Increment count for valid data points
263       }
264   }
265   var final_mean = count > 0 ? (sum / count).toFixed(3) : NaN; // Calculate mean only if count is greater than 0, otherwise set mean to NaN
266   return final_mean;
269 // Function to calculate standard deviation
270 function calculateStandardDeviation(data) {
271     var filteredData = data.map(parseFloat).filter(function(value) {
272           return !isNaN(value);
273     });
275     var mean = calculateMean(filteredData);
276     var deviation = filteredData.reduce(function (acc, val) {
277         return acc + Math.pow(val - mean, 2);
278     }, 0);
279     var sdV = Math.sqrt(deviation / filteredData.length)
280     return sdV.toFixed(3);
283 // Function to calculate percentage of missing data
284 function calculateMissingPercentage(data) {
285     // Filter out non-numeric values and convert to numbers
286     var filteredData = data.map(parseFloat).filter(function(value) {
287         return !isNaN(value);
288     });
290     // Calculate the number of missing values
291     var missingCount = data.length - filteredData.length;
293     // Calculate the missing percentage based on the length of filteredData
294     var missingPercentage = (missingCount / filteredData.length) * 100;
296     // Return the missing percentage with one decimal place
297     return missingPercentage.toFixed(1);
301 jQuery('#runAMMI').click( function () {
302     if (!jQuery('#pheno_select').val()) {
303         alert("Please select a dataset and trait.")
304         $('#stability_files').empty();
305     } else if (jQuery('#select_imput').val() == "imput_empty") {
306         alert("Please select if phenotype imputation is required.")
307     } else if (jQuery('#select_method').val() == "method_empty"){
308         alert("Please select the stability method.")
309     } else {
310         $('#stability_files').empty();
311         if ($('#pheno_select').val() != ""){
312             var dataset_id = get_dataset_id();
313             var trait_id = $('#pheno_select').val();
314             var method_id = $('#select_method').val();
315             var imput_id = $('#select_imput').val();
316             var dataset_trait_outliers = 0;
317             $.ajax({
318                 url: '/ajax/stability/generate_results',
319                 data: {
320                     'dataset_id': dataset_id,
321                     'trait_id': trait_id,
322                     'method_id':method_id,
323                     'imput_id':imput_id,
324                     'dataset_trait_outliers': dataset_trait_outliers
325                 },
326                 beforeSend: function() {
327                     jQuery("#working_modal").modal("show");
328                 },
329                 timeout: 30000000,
330                 success: function(response) {
331                     jQuery("#working_modal").modal("hide");
332                     if (response.error) {
333                         alert(response.error);
334                     } else {
335                         $(document).ready(function(){
336                             var AMMIFile_response = response.AMMITable;
337                             $.get(response.myMessage, function(data) {
338                                 if (data.length !== 9) {
339                                     alert(data);
340                                 }               
341                             });
343                             $.getJSON(response.JSONfile, function(data) {
344                                 var parsedData = JSON.parse(data);
346                                 // printing the result of imputation accuracy
347                                 if (imput_id === 'imput_yes') {
348                                     var firstLine = parsedData[0];
349                                     var imputAcc = firstLine.imputAcc;
350                                     imputAcc = imputAcc.toFixed(3);
351                                     var message = "<p>The imputation accuracy (correlation) is " + imputAcc + ".</p>";
352                                     $('#stability_message').html(message);
353                                 }else{
354                                     var message = "<p>Analysis processed with no phenotypic imputation.</p>";
355                                     $('#stability_message').html(message);
356                                 }
358                                 var table = '<table id="myTable">';
359                                 if (method_id == "ammi") {
360                                   table += '<thead class="bg-light-blue"><tr><th>Accession</th><th>Location</th><th>Effect</th><th>Stability Rank</th><th>Means</th></tr></thead>';
362                                 } else {
363                                   table += '<thead class="bg-light-blue"><tr><th>Accession</th><th>Location</th><th>Means</th><th>Location Rank</th><th>Genotype Rank</th></tr></thead>';
364                                 }
366                                 if ( method_id == "ammi"){
367                                   // Sort parsedData array by Rank in descending order
368                                   parsedData.sort(function(a, b) {
369                                       return a.Rank - b.Rank;
370                                   });
371                                 } else {
372                                   parsedData.sort(function(a, b) {
373                                       return a.locationRank - b.locationRank;
374                                   });
375                                 }
376                         
378                                 table += '<tbody>';
379                                 for (var i = 0; i < parsedData.length; i++) {
380                                     var Acc = parsedData[i].Accession;
381                                     var Loc = parsedData[i].location;
382                                     var means = parseFloat(parsedData[i].means);
383                                     var Mean = means.toFixed(3);
384                                     if (method_id == "ammi"){
385                                       var Eff = parsedData[i].Effect.toFixed(3);
386                                       var Rank = parsedData[i].Rank;
387                                     } else {
388                                       var locRank = parsedData[i].locationRank;
389                                       var genRank = parsedData[i].genotypeRank;
390                                     }
391                             
393                                     // Create a row for each data point
394                                     table += '<tr>';
395                                     table += '<td>' + Acc + '</td>';
396                                     table += '<td>' + Loc + '</td>';
397                                     
398                                     if (method_id == "ammi") {
399                                         table += '<td>' + Eff + '</td>';
400                                         table += '<td>' + Rank + '</td>';
401                                         table += '<td>' + Mean + '</td>';
402                                     } else {
403                                         table += '<td>' + Mean + '</td>';
404                                         table += '<td>' + locRank + '</td>';
405                                         table += '<td>' + genRank + '</td>';
406                                     }
407                                 
408                                     table += '</tr>';
409                                 }
410                                 table += '</tbody>';
411                                 table += '</table>';
413                                 $('#stability_output').empty().append(table);
415                                 // Initialize DataTable after the table is appended
416                                 $('#myTable').DataTable({
417                                     "paging": true, // Enable pagination
418                                     "searching": true // Enable searching
419                                 });
421                                 $('#download-button').click(function() {
422                                     var csvContent = "data:text/csv;charset=utf-8,";
423                                     // Add table headers to CSV content
425                                     if( method_id == "ammi"){
426                                         csvContent += "Accession,Location,Effect,StabilityRank,means\n";
428                                         // Add table data to CSV content
429                                         for (var i = 0; i < parsedData.length; i++) {
430                                             var row = parsedData[i];
431                                             csvContent += row.Accession + "," + row.location + "," + row.Effect + "," + row.Rank + "," + row.means + "\n";
432                                         }
433                                     } else {
434                                         csvContent += "Accession,Location,Average,locationRank,genotypeRank\n";
436                                       // Add table data to CSV content
437                                         for (var i = 0; i < parsedData.length; i++) {
438                                             var row = parsedData[i];
439                                             csvContent += row.Accession + "," + row.location + "," + row.means + "," + row.locationRank + "," + row.genotypeRank + "\n";
440                                         }
441                                     }
442                                               
443                                     // Create a data URI for the CSV content
444                                     var encodedUri = encodeURI(csvContent);
446                                     // Create a download link
447                                     var link = document.createElement("a");
448                                     link.setAttribute("href", encodedUri);
449                                     link.setAttribute("download", "stability_data.csv");
450                                     link.innerHTML = "Download CSV";
452                                     // Trigger the click event to download the file
453                                     link.click();
454                                 });
455                             });
457                             $.getJSON(response.myGraph, function(data) {
458                                 var parseData = JSON.parse(data);
459                                 // Create a Set to store unique accessions
460                                 var uniqueAccessions = new Set();
462                                 // Iterate through parseData to collect unique accessions
463                                 for (var i = 0; i < parseData.length; i++) {
464                                     uniqueAccessions.add(parseData[i].Accession);
465                                 }
467                                 // Convert Set back to an array
468                                 var uniqueAccessionsArray = Array.from(uniqueAccessions);
470                                 // Populate the select box with unique accessions
471                                 var select = document.getElementById("accession_select");
473                                 // Clear existing options
474                                 select.innerHTML = "";
476                                 // Add each unique accession as an option
477                                 for (var j = 0; j < uniqueAccessionsArray.length; j++) {
478                                     var option = document.createElement("option");
479                                     option.text = uniqueAccessionsArray[j];
480                                     option.value = uniqueAccessionsArray[j];
481                                     select.add(option);
482                                 }
484                                 // Define function to redraw chart based on selected varieties
485                                 function redrawChart() {
486                                     // Get selected varieties from the select box
487                                     const selectedVarieties = Array.from(select.selectedOptions).map(option => option.value);
489                                     // Filter the data based on the selected varieties
490                                     const filteredData = parseData.filter(d => selectedVarieties.includes(d.Accession));
492                                     // Remove the existing plot
493                                     d3.select("#graphics_output svg").remove();
495                                     // Call createChart or deviationLinePlot function with the filtered data
496                                     if (method_id === "ammi") {
497                                         createChart(filteredData); // Pass filtered data
498                                     } else {
499                                         deviationLinePlot(filteredData); // Pass filtered data
500                                     }
501                                 }
504                                 // Attach event listener to select box for automatic redraw
505                                 select.addEventListener('change', redrawChart);
507                                 let svgElement;
508                                 if ( method_id == "ammi"){
509                                     svgElement = createChart(parseData);
510                                 } else {
511                                     svgElement = deviationLinePlot(parseData);
512                                 }
513                                 $('#graphics_output').append(svgElement);
514                             });
515                         })
516              
517             }
518           },
519           error: function(response) {
520             alert("An error occurred, the service may temporarily be unavailable");
521           }
522         });
523       }
524     };
528 function createChart(data) {
529     // Set up SVG dimensions
530     const margin = { top: 10, right: 120, bottom: 40, left: 50 };
531     const width = 600 - margin.left - margin.right;
532     const height = 400 - margin.top - margin.bottom;
534     // Create SVG element
535     const svg = d3.select("#graphics_output")
536         .append("svg")
537         .attr("width", width + margin.left + margin.right)
538         .attr("height", height + margin.top + margin.bottom)
539         .append("g")
540         .attr("transform", `translate(${margin.left},${margin.top})`);
542     // Define scales
543     const xScale = d3.scaleLinear()
544         .domain([d3.min(data, d => d.X), d3.max(data, d => d.X)])
545         .range([0, width]);
547     // Find the minimum and maximum y-values in your data
548     const minY = d3.min(data, d => d.value);
549     const maxY = d3.max(data, d => d.value);
551     // Define a linear scale to map the original y-values to a range between 0 and 1
552     const yScale = d3.scaleLinear()
553         .domain([0, 1]) // Adjusted domain for y-axis
554         .range([height, 0]);
556     // Define line function
557     const line = d3.line()
558         .x(d => xScale(d.X))
559         .y(d => yScale(d.value));
561     // Define color scale
562     const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
564     // Create a tooltip div
565     const tooltip = d3.select("body").append("div")
566         .attr("class", "tooltip")
567         .style("opacity", 0);
569     // Append lines to the SVG
570     const lines = svg.selectAll(".line")
571         .data(d3.nest()
572             .key(d => d.Accession)
573             .entries(data))
574         .enter().append("g");
576     lines.append("path")
577         .attr("class", "line")
578         .attr("d", d => line(d.values))
579         .attr("fill", "none")
580         .attr("stroke", d => colorScale(d.key))
581         .on("mouseover", function (event, d) {
582             d3.select(this).attr("stroke-width", 4);
583             tooltip.transition()
584                 .duration(200)
585                 .style("opacity", .9);
586             tooltip.html("Accession: " + d.key) // Display accession name in the tooltip
587                 .style("left", (event.pageX + 10) + "px")
588                 .style("top", (event.pageY - 20) + "px");
589         })
590         .on("mouseout", function () {
591             d3.select(this).attr("stroke-width", 2);
592             tooltip.transition()
593                 .duration(500)
594                 .style("opacity", 0);
595         });
597     // Append variety names at the end of each line
598     lines.each(function (d) {
599         const endData = d.values[d.values.length - 1];
600         svg.append("text")
601             .attr("class", "variety-name")
602             .attr("x", xScale(endData.X) + 5)
603             .attr("y", yScale(endData.value))
604             .text(endData.Accession) // Using "Accession" property
605             .attr("fill", colorScale(d.key));
606     });
608     // Plot points
609     svg.selectAll(".point")
610         .data(data)
611         .enter().append("circle")
612         .attr("class", "point")
613         .attr("cx", d => xScale(d.X))
614         .attr("cy", d => yScale(d.value))
615         .attr("r", 3)
616         .attr("fill", d => colorScale(d.Accession));
618     // Add axes
619     svg.append("g")
620         .attr("class", "x-axis")
621         .attr("transform", `translate(0,${height})`)
622         .call(d3.axisBottom(xScale).ticks(5))
623         .selectAll('path')
624         .style('stroke-width', '2px');
626     svg.append("g")
627         .attr("class", "y-axis")
628         .call(d3.axisLeft(yScale))
629         .selectAll('path')
630         .style('stroke-width', '2px');
632     // Add labels and title
633     svg.append("text")
634         .attr("class", "title")
635         .attr("x", width / 2)
636         .attr("y", -margin.top / 2)
637         .style("text-anchor", "middle")
638         // .text("Means vs. Value X Grouped by Accessions");
640     svg.append("text")
641         .attr("class", "x-label")
642         .attr("x", width / 2)
643         .attr("y", height + margin.bottom)
644         .style("text-anchor", "middle")
645         .text("Number of Locations");
647     svg.append("text")
648         .attr("class", "y-label")
649         .attr("transform", "rotate(-90)")
650         .attr("x", -height / 2)
651         .attr("y", -margin.left)
652         .attr("dy", "1em")
653         .style("text-anchor", "middle")
654         .text("Scaled Stability");
656     return svg; // Return the SVG node
660 function deviationLinePlot(data) {
661     // Set up SVG dimensions
662     const margin = { top: 10, right: 120, bottom: 40, left: 50 };
663     const width = 600 - margin.left - margin.right;
664     const height = 400 - margin.top - margin.bottom;
666     // Extract unique accessions and locations
667     const accessions = [...new Set(data.map(d => d.Accession))];
668     const locations = [...new Set(data.map(d => d.location))];
670     // Define scales
671     const xScale = d3.scalePoint()
672         .domain(locations)
673         .range([0, width])
674         .padding(0.5);
676     const buffer = 20;
677     const yMin = d3.min(data, d => d.mean - d.sd);
678     const yMax = d3.max(data, d => d.mean + d.sd);
679     const yDomainMin = Math.max(yMin*0.9);
680     const yScale = d3.scaleLinear()
681         .domain([yDomainMin, yMax + buffer])
682         .range([height, 0]);
685     // Define line generator
686     const line = d3.line()
687         .x(d => xScale(d.location))
688         .y(d => yScale(d.mean));
690     // Define color scale
691     const colorScale = d3.scaleOrdinal(d3.schemeCategory10);
693     // Create SVG element
694     const svg = d3.select("#graphics_output")
695         .append("svg")
696         .attr("width", width + margin.left + margin.right)
697         .attr("height", height + margin.top + margin.bottom)
698         .append("g")
699         .attr("transform", `translate(${margin.left},${margin.top})`);
701     // Add lines for each accession
702     accessions.forEach(accession => {
703         const accessionData = data.filter(d => d.Accession === accession);
704         svg.append("path")
705             .datum(accessionData)
706             .attr("class", "line")
707             .attr("d", line)
708             .attr("fill", "none")
709             .attr("stroke", colorScale(accession));
711         // Add accession name at the end of the line
712         const lastPoint = accessionData[accessionData.length - 1];
713         svg.append("text")
714             .attr("x", xScale(lastPoint.location) + 5)
715             .attr("y", yScale(lastPoint.mean))
716             .attr("dy", "0.35em")
717             .attr("fill", colorScale(accession))
718             .text(accession);
719     });
721     // Add points at each location
722     svg.selectAll(".point")
723         .data(data)
724         .enter().append("circle")
725         .attr("class", "point")
726         .attr("cx", d => xScale(d.location))
727         .attr("cy", d => yScale(d.mean))
728         .attr("r", 3)
729         .attr("fill", d => colorScale(d.Accession)); // Use colorScale to color points
731     // Add error bars
732     svg.selectAll(".error-bar")
733         .data(data)
734         .enter().append("line")
735         .attr("class", "error-bar")
736         .attr("x1", d => xScale(d.location))
737         .attr("y1", d => yScale(d.mean + d.sd))
738         .attr("x2", d => xScale(d.location))
739         .attr("y2", d => yScale(d.mean - d.sd))
740         .attr("stroke", d => colorScale(d.Accession)) // Use colorScale to set stroke color
741         .attr("stroke-width", 1);
744     // Add axes
745     svg.append("g")
746         .attr("class", "x-axis")
747         .attr("transform", `translate(0,${height})`)
748         .call(d3.axisBottom(xScale));
750     svg.append("g")
751         .attr("class", "y-axis")
752         .call(d3.axisLeft(yScale));
754     // Add labels and title
755     svg.append("text")
756         .attr("class", "title")
757         .attr("x", width / 2)
758         .attr("y", -margin.top / 2)
759         .style("text-anchor", "middle");
761     svg.append("text")
762         .attr("class", "x-label")
763         .attr("x", width / 2)
764         .attr("y", height + margin.bottom)
765         .style("text-anchor", "middle")
766         .text("Location");
768     svg.append("text")
769         .attr("class", "y-label")
770         .attr("transform", "rotate(-90)")
771         .attr("x", -height / 2)
772         .attr("y", -margin.left)
773         .attr("dy", "1em")
774         .style("text-anchor", "middle")
775         .text("Mean");
777     return svg; // Return the SVG node
781 function get_dataset_id() {
782       var selected_datasets = [];
783       jQuery('input[name="dataset_select_checkbox"]:checked').each(function() {
784           selected_datasets.push(jQuery(this).val());
785       });
786       if (selected_datasets.length < 1){
787           alert('Please select at least one dataset!');
788           return false;
789       } else if (selected_datasets.length > 1){
790           alert('Please select only one dataset!');
791           return false;
792       } else {
793       var dataset_id=selected_datasets[0];
794       return dataset_id;
795     }
798   }
803 </script>
806 <!-- STYLE -->
807 <style>
809 h1 {
810   display:none;
813 .bg-light-blue {
814     background-color: #EEEEFE;
817 /* Style the background color of the search input field */
818 .dataTables_wrapper .dataTables_filter input[type="search"] {
819     background-color: #add8e6; /* Slightly darker shade of light blue */
820     border-color: #add8e6; /* Match the border color to the background color */
824 .seq_map {
825   color: #777777;
826   width: 700px;
827   position:relative;
828   overflow: auto;
829   align: left;
832 .blast_select_box {
833   width:300px;
834   margin-right:10px;
837 .blast_select_label {
838   width:100px;
839   margin-right:10px;
840   line-height: 32px;
843 .ui-dialog {
844   position:relative;
847 #region_square {
848   position:absolute;
849   vertical-align:middle;
851 .help_dialog {
852   color:blue;
853   cursor:pointer
855 #desc_dialog {
856   overflow: auto;
857   position: relative;
859 .help_box {
860   background-color:#EEEEFE;
861   border-color:#AAA;
862   border-width:2px;
863   border-style:solid;
864   border-radius:5px;
865   padding-left: 10px;
866   padding-right: 10px;
869 #sequence {
870   min-height: 80px;
871   max-height: 300px;
872 /*  min-width: 700px;*/
873   max-width: 98%;
876 .download_tag {
877   display:none;
880 /* BLAST canvas Graph */
882 .width-1000 {
883   width: 1000px;
884   text-align: center;
887 #sgn_blast_graph {
888   overflow:hidden;
891 #myCanvas {
892 /*  border-style: solid;*/
893 /*  border-width: 1px;*/
894 /*  border-color: #ddd;*/
895 /*  border-width:0px 1px 1px 1px;*/
896   height:450px;
897   width:1020px;
898   overflow:scroll;
899   overflow-x: hidden;
903 #tooltip {
904   position: absolute;
905   visibility: hidden;
906   background-color: white;
907   border: 1px solid #ccc;
908   padding: 10px;
909   z-index: 1;
912 #stability_message_container {
913     text-align: left;
917 table {
918     border-collapse: collapse;
919     width: 100%;
922 table, th, td {
923     border: 1px solid black;
926 th, td {
927     padding: 8px;
928     text-align: left;
931 th {
932     background-color: #f2f2f2;
937 </style>