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>
34 <button class="btn btn-primary" id="selectDataset" type="submit" name="selectDataset" value="selectDataset">Select Dataset</button>
37 <div class="form-group form-inline">
38 <label class="blast_select_label" for = pheno_select>Available Traits: </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 Traits: </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> -->
47 <div id="pheno_summary_table">
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>
60 <div id = "select_method">
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>
70 <div id = "select_method">
74 <div style="text-align: center">
75 <button class="btn btn-primary" id="runAMMI" type="submit" name="runAMMI" value="runAMMI">Run Stability</button>
78 <div id="tempfile" style="display:none" >
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⇣</a> <a id="download_basic" class="download_tag" target="_blank" href="javascript:download();" title="Download results in basic format">Basic⇣</a>' &>
83 <div id="sgn_blast_graph" style="display:none">
85 Your browser does not support the HTML5 canvas
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>
101 <div id="stability_summary"></div>
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⇣</a> <a id="download_basic" class="download_tag" target="_blank" href="javascript:download();" title="Download results in basic format">Basic⇣</a>' &>
106 <div id="stability_message_container">
107 <div id="stability_message"></div>
111 <div id="stability_output"></div>
114 <button class="btn btn-primary" id="download-button">Download CSV</button>
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⇣</a> <a id="download_basic" class="download_tag" target="_blank" href="javascript:download();" title="Download results in basic format">Basic⇣</a>' &>
118 <div style="display: flex;">
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 -->
125 <div style="margin-left: 25px;">
128 <svg id="graphics_output" width="700" height="600"></svg>
129 <div id="tooltip"></div>
138 jQuery(document).ready(function() {
140 get_select_box("datasets", "dataset_select", {"checkbox_name":"dataset_select_checkbox"});
143 alert('You must be logged in to use Stability');
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();
151 jQuery('#selectDataset').click(function() {
152 var dataset_id = get_dataset_id();
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');
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>';
165 $('#pheno_select').attr("disabled",false).html(option_html);
166 jQuery('#tempfile').html(response.tempfile);
169 error: function(response) {
170 alert("An error occurred, please check the datase.");
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];
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);
212 console.error("Invalid response format.");
215 .fail(function(jqXHR, textStatus, errorThrown) {
216 console.error("Error fetching file:", errorThrown);
219 // If pheno_select is empty, clear the table
220 $('#pheno_summary_table').html('');
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);
231 // Calculate minimum from filtered data
232 var min = Math.min.apply(null, filteredData).toFixed(3);
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);
244 // Calculate minimum from filtered data
245 var min = Math.max.apply(null, filteredData).toFixed(3);
250 function calculateMean(data) {
251 var filteredData = data.map(parseFloat).filter(function(value) {
252 return !isNaN(value);
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
262 count++; // Increment count for valid data points
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
269 // Function to calculate standard deviation
270 function calculateStandardDeviation(data) {
271 var filteredData = data.map(parseFloat).filter(function(value) {
272 return !isNaN(value);
275 var mean = calculateMean(filteredData);
276 var deviation = filteredData.reduce(function (acc, val) {
277 return acc + Math.pow(val - mean, 2);
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);
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.")
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;
318 url: '/ajax/stability/generate_results',
320 'dataset_id': dataset_id,
321 'trait_id': trait_id,
322 'method_id':method_id,
324 'dataset_trait_outliers': dataset_trait_outliers
326 beforeSend: function() {
327 jQuery("#working_modal").modal("show");
330 success: function(response) {
331 jQuery("#working_modal").modal("hide");
332 if (response.error) {
333 alert(response.error);
335 $(document).ready(function(){
336 var AMMIFile_response = response.AMMITable;
337 $.get(response.myMessage, function(data) {
338 if (data.length !== 9) {
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);
354 var message = "<p>Analysis processed with no phenotypic imputation.</p>";
355 $('#stability_message').html(message);
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>';
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>';
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;
372 parsedData.sort(function(a, b) {
373 return a.locationRank - b.locationRank;
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;
388 var locRank = parsedData[i].locationRank;
389 var genRank = parsedData[i].genotypeRank;
393 // Create a row for each data point
395 table += '<td>' + Acc + '</td>';
396 table += '<td>' + Loc + '</td>';
398 if (method_id == "ammi") {
399 table += '<td>' + Eff + '</td>';
400 table += '<td>' + Rank + '</td>';
401 table += '<td>' + Mean + '</td>';
403 table += '<td>' + Mean + '</td>';
404 table += '<td>' + locRank + '</td>';
405 table += '<td>' + genRank + '</td>';
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
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";
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";
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
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);
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];
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
499 deviationLinePlot(filteredData); // Pass filtered data
504 // Attach event listener to select box for automatic redraw
505 select.addEventListener('change', redrawChart);
508 if ( method_id == "ammi"){
509 svgElement = createChart(parseData);
511 svgElement = deviationLinePlot(parseData);
513 $('#graphics_output').append(svgElement);
519 error: function(response) {
520 alert("An error occurred, the service may temporarily be unavailable");
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")
537 .attr("width", width + margin.left + margin.right)
538 .attr("height", height + margin.top + margin.bottom)
540 .attr("transform", `translate(${margin.left},${margin.top})`);
543 const xScale = d3.scaleLinear()
544 .domain([d3.min(data, d => d.X), d3.max(data, d => d.X)])
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
556 // Define line function
557 const line = d3.line()
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")
572 .key(d => d.Accession)
574 .enter().append("g");
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);
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");
590 .on("mouseout", function () {
591 d3.select(this).attr("stroke-width", 2);
594 .style("opacity", 0);
597 // Append variety names at the end of each line
598 lines.each(function (d) {
599 const endData = d.values[d.values.length - 1];
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));
609 svg.selectAll(".point")
611 .enter().append("circle")
612 .attr("class", "point")
613 .attr("cx", d => xScale(d.X))
614 .attr("cy", d => yScale(d.value))
616 .attr("fill", d => colorScale(d.Accession));
620 .attr("class", "x-axis")
621 .attr("transform", `translate(0,${height})`)
622 .call(d3.axisBottom(xScale).ticks(5))
624 .style('stroke-width', '2px');
627 .attr("class", "y-axis")
628 .call(d3.axisLeft(yScale))
630 .style('stroke-width', '2px');
632 // Add labels and title
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");
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");
648 .attr("class", "y-label")
649 .attr("transform", "rotate(-90)")
650 .attr("x", -height / 2)
651 .attr("y", -margin.left)
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))];
671 const xScale = d3.scalePoint()
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])
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")
696 .attr("width", width + margin.left + margin.right)
697 .attr("height", height + margin.top + margin.bottom)
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);
705 .datum(accessionData)
706 .attr("class", "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];
714 .attr("x", xScale(lastPoint.location) + 5)
715 .attr("y", yScale(lastPoint.mean))
716 .attr("dy", "0.35em")
717 .attr("fill", colorScale(accession))
721 // Add points at each location
722 svg.selectAll(".point")
724 .enter().append("circle")
725 .attr("class", "point")
726 .attr("cx", d => xScale(d.location))
727 .attr("cy", d => yScale(d.mean))
729 .attr("fill", d => colorScale(d.Accession)); // Use colorScale to color points
732 svg.selectAll(".error-bar")
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);
746 .attr("class", "x-axis")
747 .attr("transform", `translate(0,${height})`)
748 .call(d3.axisBottom(xScale));
751 .attr("class", "y-axis")
752 .call(d3.axisLeft(yScale));
754 // Add labels and title
756 .attr("class", "title")
757 .attr("x", width / 2)
758 .attr("y", -margin.top / 2)
759 .style("text-anchor", "middle");
762 .attr("class", "x-label")
763 .attr("x", width / 2)
764 .attr("y", height + margin.bottom)
765 .style("text-anchor", "middle")
769 .attr("class", "y-label")
770 .attr("transform", "rotate(-90)")
771 .attr("x", -height / 2)
772 .attr("y", -margin.left)
774 .style("text-anchor", "middle")
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());
786 if (selected_datasets.length < 1){
787 alert('Please select at least one dataset!');
789 } else if (selected_datasets.length > 1){
790 alert('Please select only one dataset!');
793 var dataset_id=selected_datasets[0];
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 */
837 .blast_select_label {
849 vertical-align:middle;
860 background-color:#EEEEFE;
872 /* min-width: 700px;*/
880 /* BLAST canvas Graph */
892 /* border-style: solid;*/
893 /* border-width: 1px;*/
894 /* border-color: #ddd;*/
895 /* border-width:0px 1px 1px 1px;*/
906 background-color: white;
907 border: 1px solid #ccc;
912 #stability_message_container {
918 border-collapse: collapse;
923 border: 1px solid black;
932 background-color: #f2f2f2;