Skip to content

Commit

Permalink
ENH: group BUSCO stats into collapsible sections for better display (#…
Browse files Browse the repository at this point in the history
…148)

Co-authored-by: Santiago Castro Dau <[email protected]>
  • Loading branch information
misialq and Sann5 authored May 1, 2024
1 parent 89b9115 commit 897d7e0
Show file tree
Hide file tree
Showing 47 changed files with 2,336 additions and 1,685 deletions.
88 changes: 88 additions & 0 deletions q2_moshpit/assets/busco/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,91 @@
margin-top: 8px;
margin-bottom: 8px;
}

/*adjustment for Bootstrap 5*/
.table {
border: 1px solid #dee2e6;
margin-top: 10px
}

.pagination {
justify-content: right;
}

/* SPINKIT */

/*
The MIT License (MIT)
Copyright (c) 2015-2020 Tobias Ahlin
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

.spinner {
margin: 100px auto;
width: 50px;
height: 40px;
text-align: center;
font-size: 10px;
}

.spinner > div {
background-color: #333;
height: 100%;
width: 6px;
display: inline-block;

-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

.spinner .rect2 {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}

.spinner .rect3 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}

.spinner .rect4 {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}

.spinner .rect5 {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
20% { -webkit-transform: scaleY(1.0) }
}

@keyframes sk-stretchdelay {
0%, 40%, 100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
} 20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}
258 changes: 258 additions & 0 deletions q2_moshpit/assets/busco/detailed_view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
{% extends "tabbed.html" %}

{% block head %}
<title>Embedding Vega-Lite</title>
<script src="js/bootstrapMagic.js" type="text/javascript"></script>
<link href="css/styles.css" rel="stylesheet" />
<script type="text/javascript">
// temporary hack to make it look good with Bootstrap 5
removeBS3refs();
</script>
<script src="https://cdn.jsdelivr.net/npm//vega@5" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm//[email protected]" type="text/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm//vega-embed@6" type="text/javascript"></script>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha256-YvdLHPgkqJ8DVUxjjnGVlMMJtNimJ6dYkowFFvp4kKs=" rel="stylesheet"/>
{% endblock %}

{% block tabcontent %}
<script crossorigin="anonymous" integrity="sha256-9SEPo+fwJFpMUet/KACSwO+Z/dKMReF9q4zFhU/fT9M=" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>

<br>
<div class="row row-cols-1 row-cols-md-2 g-4">
<div class="col-lg-12">
<div class="card mt-3 h-10">
<h5 class="card-header">Plot description</h5>
<div class="card-body">
<p>
The left plot shows the results generated by BUSCO for <b>all MAGs</b> and
<b> samples</b>. "BUSCO attempts to provide a quantitative assessment
of the completeness in terms of the expected gene content of a genome
assembly, transcriptome, or annotated gene set. The results are
simplified into categories of complete and single-copy, complete and
duplicated, fragmented, or missing BUSCOs. BUSCO completeness results
make sense only in the context of the biology of your organism". Visit the
<a href="https://busco.ezlab.org/busco_userguide.html#interpreting-the-results">BUSCO User Guide </a>
for more information.
</p>
<p>
The right barplot shows assembly statistics calculated for each MAG using BBTools.
Specifically, it displays the statistics computed by the <b>stats.sh</b> procedure from BBMap.
View the
<a href="https://github.com/BioInfoTools/BBMap/blob/master/sh/stats.sh">
source code and documentation
</a>
of stats.sh for more information.
</p>

<div style="align-items: center; display: flex">
<span class="header-inline">Downloads:</span>
<div class="'col-lg-4">
<div aria-label="Basic outlined example" class="btn-group" role="group">
<a class="btn btn-outline-secondary" href="busco_results.csv">BUSCO results (csv)</a>
</div>
</div>
</div>
</div>
</div>

<div class="card mt-3 h-10">
<h5 class="card-header">Plot controls</h5>
<div class="card-body">
<p>
To prevent data overload, the samples are grouped into collapsible sections below. You can
expand/collapse each section by clicking on the little arrow on the right side of the section header.
Each section will contain a maximum of 100 bins.
<br><br>
If you want to find statistics for a specific sample, you can pick your sample from the dropdown below -
this will find the appropriate section and expand it.
<br><br>
You can hover over the bars to obtain information about the lineage dataset used for each MAG,
and the number of genes in each BUSCO category. Additionally, you can choose the assembly statistic
that you wish to display from the dropdown menu included in every section.
</p>
<div id="plot-controls">
<div style="align-items: center; display: flex;">
<span class="header-inline">Find sample:</span>
<div class="col-lg-4">
<div class="btn-group" role="group">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="sampleSelector" data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1" id="sampleSelectorElements"></ul>
</div>
<button id="collapseAllButton" class="btn btn-outline-secondary" type="submit" style="white-space: nowrap;">Collapse all</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-lg-12">
<div id="plot">
<div class="accordion" id="accordionSamples"></div>
</div>
</div>
</div>

<script id="vega_json" type="application/json">
{{ vega_json | safe }}
</script>

<script type="text/javascript">
function createAccordionItem(sampleFrom, sampleTo, show) {
// Create elements
const accordionItem = document.createElement('div');
const accordionHeader = document.createElement('h2');
const accordionButton = document.createElement('button');
const accordionCollapse = document.createElement('div');
const accordionBody = document.createElement('div');

// Set attributes and content
accordionItem.className = 'accordion-item';
accordionHeader.className = 'accordion-header';
// accordionHeader.id = 'headingOne';
accordionButton.className = 'accordion-button';
accordionButton.type = 'button';
accordionButton.setAttribute('data-bs-toggle', 'collapse');
accordionButton.setAttribute('data-bs-target', `#collapse-${sampleFrom}`);
// accordionButton.setAttribute('aria-expanded', 'true');
accordionButton.setAttribute('aria-controls', `collapse-${sampleFrom}`);
accordionButton.textContent = `Samples ${sampleFrom} to ${sampleTo}`;
accordionCollapse.id = `collapse-${sampleFrom}`;
if (show) {
accordionCollapse.className = 'accordion-collapse collapse show';
} else {
accordionCollapse.className = 'accordion-collapse collapse';
}
// accordionCollapse.setAttribute('aria-labelledby', 'headingOne');
accordionCollapse.setAttribute('data-bs-parent', '#accordionSamples');
accordionBody.className = 'accordion-body';
accordionBody.id = `accordion-${sampleFrom}`;

// Append elements
accordionHeader.appendChild(accordionButton);
accordionItem.appendChild(accordionHeader);
accordionCollapse.appendChild(accordionBody);
accordionItem.appendChild(accordionCollapse);

return accordionItem;
}

function createDropdownItem(sampleName) {
const listItem = document.createElement("li");
const link = document.createElement("a");
link.className = "dropdown-item";
link.href = "#";
link.textContent = sampleName;
listItem.appendChild(link);
return listItem;
}

function populateDropdown(spec) {
const sampleSelector = document.getElementById("sampleSelectorElements");
let samples = [];
Object.entries(spec).forEach(
([_, specElement]) => {
let sampleIds = specElement["sample_ids"];
samples = [...samples, ...sampleIds];
}
);
console.log("All the samples:", samples);
for (let i = 0; i < samples.length; i++) {
const listItem = createDropdownItem(samples[i]);
sampleSelector.appendChild(listItem);
}
document.getElementById("sampleSelector").textContent = samples[0];
}

function findFromValue(dictionary, sampleId) {
for (let [key, value] of Object.entries(dictionary)) {
if (value.sample_ids.includes(sampleId)) {
return value.sample_counter.from;
}
}
return null; // return null if the sampleId is not found in any sample_ids array
}

function handleDropdownClick(event, spec, collapsibles) {
const sampleName = event.target.innerText; // Get value from dataset
const fromValue = findFromValue(spec, sampleName);

// Update the dropdown button text
document.getElementById("sampleSelector").textContent = sampleName;

// Hide all collapsibles
collapsibles.forEach(collapsible => {
collapsible.classList.remove("show");
});

// Show the selected collapsible (if a value is selected)
if (fromValue) {
const targetCollapsible = document.getElementById(`collapse-${fromValue}`);
targetCollapsible.classList.add('show');
}
}

$(document).ready(function () {
// temporary hack to make it look good with Bootstrap 5
adjustTagsToBS3()

const spec = JSON.parse(document.getElementById('vega_json').textContent);
let count = 0;
Object.entries(spec).forEach(([_, specElement]) => {
const sampleCounter = specElement["sample_counter"];
const sampleFrom = sampleCounter["from"];
const sampleTo = sampleCounter["to"];

if (count === 0) {
document.getElementById("accordionSamples")
.appendChild(createAccordionItem(sampleFrom, sampleTo, true));
} else {
document.getElementById("accordionSamples")
.appendChild(createAccordionItem(sampleFrom, sampleTo, false));
}
count += 1;

vegaEmbed(`#accordion-${sampleFrom}`, specElement["subcontext"]).then(
function (result) {
result.view.logLevel(vega.Warn);
window.v = result.view;
}
).catch(
function (error) {
handleErrors([error], $(`#accordion-${sampleFrom}`));
}
);
});

populateDropdown(spec);

const dropdownMenu = document.getElementById("sampleSelectorElements");
const collapsibles = document.querySelectorAll(".collapse.accordion-collapse");

// Add event listener to the dropdown menu (ul)
dropdownMenu.addEventListener('click', function(event) {
handleDropdownClick(event, spec, collapsibles);
});

const collapseAllButton = document.getElementById("collapseAllButton");
// Add event listener to thecollapse button
collapseAllButton.addEventListener('click', function(event) {
collapsibles.forEach(collapsible => {
collapsible.classList.remove("show");
});
});

});
</script>

{% endblock %}

{% block footer %}
{% set loading_selector = '#loading' %}
{% include 'js-error-handler.html' %}
{% endblock %}
Loading

0 comments on commit 897d7e0

Please sign in to comment.