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
  • Loading branch information
misialq committed Mar 14, 2024
1 parent df1301d commit 11ef172
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 98 deletions.
271 changes: 190 additions & 81 deletions q2_moshpit/assets/busco/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,87 +6,78 @@
// 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"
/>
<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 content %}
<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>
<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>

<div class="row row-cols-1 row-cols-md-2 g-4">
<div class="col-lg-12">
<div class="card mt-3 h-100">
<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 bins</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
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
>
<a href="https://busco.ezlab.org/busco_userguide.html#interpreting-the-results">BUSCO User Guide </a>
for more information.
</p>
<p>
Hoover over the graph to obtain information about the lineage dataset
used for each bin, and the number of genes in each BUSCO category.
</p>
<p>
The right barplot shows assembly statistics calculated for each bin 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"
>
<a href="https://github.com/BioInfoTools/BBMap/blob/master/sh/stats.sh">
source code and documentation
</a>
of stats.sh for more information.
</p>
<p>
Choose the assembly statistic that you wish to display from the drop-down manu below the graphs.
Hoover over the graph to show the numerical values that each bar represents.
</p>

<div style="align-items: center; display: flex">
<span class="header-inline">Downloads</span>
<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="all_batch_summeries.csv"
>BUSCO batch summary for all samples (csv)</a
>
<a class="btn btn-outline-secondary" href="BUSCO_plots.zip"
>BUSCO plots for all samples (zip)</a
>
<div aria-label="Basic outlined example" class="btn-group" role="group">
<a class="btn btn-outline-secondary" href="all_batch_summaries.csv">BUSCO batch summary for all samples (csv)</a>
<a class="btn btn-outline-secondary" href="BUSCO_plots.zip">BUSCO plots for all samples (zip)</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 bin,
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>
Expand All @@ -96,43 +87,161 @@ <h5 class="card-header">Plot description</h5>
</div>

<div class="row">
{% if vega_plots_overview is defined %}
<div class="col-lg-6">
<div id="plot"></div>
<div id="plot-controls"></div>
<div class="col-lg-12">
<div id="plot">
<div class="accordion" id="accordionSamples"></div>
</div>
</div>
{% else %}
<p>Unable to generate the completeness plot</p>
{% endif %}
</div>

{% if vega_plots_overview is defined %}
<script id="spec" type="application/json">
{{
vega_plots_overview
}}
<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 () {
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;

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

vegaEmbed("#plot", spec)
.then(function (result) {
result.view.logLevel(vega.Warn);
window.v = result.view;
populateDropdown(spec);

// move the sliders to the right
const controls = document.getElementsByClassName("vega-bindings");
document.getElementById("plot-controls").appendChild(controls[0]);
})
.catch(function (error) {
// From 'js-error-handler.html'
handleErrors([error], $("#plot"));
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>

{% endif %} {% endblock %} {% block footer %} {% set loading_selector =
{% endblock %} {% block footer %} {% set loading_selector =
'#loading' %} {% include 'js-error-handler.html' %} {% endblock %}
16 changes: 7 additions & 9 deletions q2_moshpit/busco/busco.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
#
# The full license is in the file LICENSE, distributed with this software.
# ----------------------------------------------------------------------------


import os
import tempfile
import q2_moshpit.busco.utils
from q2_moshpit.busco.utils import (
_parse_busco_params,
_render_html,
_parse_busco_params, _render_html, _run_busco,
_collect_summaries_and_save, _draw_busco_plots,
_zip_busco_plots,
)
from q2_moshpit._utils import _process_common_input_params
from q2_types.per_sample_sequences._format import MultiMAGSequencesDirFmt
Expand Down Expand Up @@ -71,7 +69,7 @@ def evaluate_busco(
# Run busco for every sample. Returns dictionary to report files.
# Result NOT included in final output
busco_results_dir = os.path.join(tmp, "busco_output")
path_to_run_summaries = q2_moshpit.busco.utils._run_busco(
path_to_run_summaries = _run_busco(
output_dir=busco_results_dir,
mags=bins,
params=common_args,
Expand All @@ -82,23 +80,23 @@ def evaluate_busco(
all_summaries_path = os.path.join(
output_dir, "all_batch_summaries.csv"
)
all_summaries_df = q2_moshpit.busco.utils._collect_summaries_and_save(
all_summaries_df = _collect_summaries_and_save(
all_summaries_path=all_summaries_path,
path_to_run_summaries=path_to_run_summaries,
)

# Draw BUSCO plots for all samples
# Result NOT included in final output
plots_dir = os.path.join(tmp, "plots")
paths_to_plots = q2_moshpit.busco.utils._draw_busco_plots(
paths_to_plots = _draw_busco_plots(
path_to_run_summaries=path_to_run_summaries,
plots_dir=plots_dir
)

# Zip graphs for user download
# Result included in final output (file for download)
zip_name = os.path.join(output_dir, "busco_plots.zip")
q2_moshpit.busco.utils._zip_busco_plots(
_zip_busco_plots(
paths_to_plots=paths_to_plots,
zip_path=zip_name
)
Expand Down
Loading

0 comments on commit 11ef172

Please sign in to comment.