Commit 5c00ac74 authored by Wai Yi Leung's avatar Wai Yi Leung
Browse files

Gears report setup

parent e88b3068
// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = {
w: 130, h: 20, s: 3, t: 10
};
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray, percentageString) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.name + d.depth; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", function(d) { return color((d.children ? d : d.parent).name); });
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
// Set position for entering and updating nodes.
g.attr("transform", function(d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseover(d) {
var percentage = (100 * d.value / totalSize).toPrecision(3);
var percentageString = percentage + "%";
if (percentage < 0.1) {
percentageString = "< 0.1%";
}
d3.select("#percentage")
.text(percentageString);
d3.select("#explanation")
.style("visibility", "");
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray, percentageString);
// Fade all the segments.
d3.selectAll("path")
.style("opacity", 0.3);
// Then highlight only those that are an ancestor of the current segment.
svg.selectAll("path")
.filter(function(node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style("opacity", 1);
}
// Restore everything to full opacity when moving off the visualization.
function mouseleave(d) {
// Hide the breadcrumb trail
d3.select("#trail")
.style("visibility", "hidden");
// Deactivate all segments during transition.
d3.selectAll("path").on("mouseover", null);
// Transition each segment to full opacity and then reactivate it.
d3.selectAll("path")
.transition()
.duration(500)
.style("opacity", 1)
.each("end", function() {
d3.select(this).on("mouseover", mouseover);
});
d3.select("#explanation")
.style("visibility", "hidden");
}
function mousein(d) {
d3.select("#selection_name").text(d.name);
d3.select("#selection_value").text(d.value);
d3.select("#selection_size").text(d.size);
mouseover(d);
}
function mouseout(d) {
d3.select("#selection_name").text("");
d3.select("#selection_value").text("");
d3.select("#selection_size").text("");
//mouseleave(d);
}
function loadTable(d, target) {
var content = "";
console.log(d);
d3.select(target).html("");
var table = d3.select("#datatable").append("table").attr("class", "table");
var row;
row = table.append("tr");
row.append("th").text("Name");
row.append("th").text("Clade count");
row.append("th").text("Hits on this leaf");
for( var i in d.children ) {
var child = d.children[ i ];
row = table.append("tr");
row.append("td").attr("class", "col-md-6").append("a").text( child.name )
.data(child)
.on("click", outerclick);
row.append("td").attr("class", "col-md-3").text( child.size );
row.append("td").attr("class", "col-md-3").text( child.value );
}
}
function outerclick(d) {
var path = svg.datum(node).selectAll("path");
node = d;
path.transition()
.duration(1000)
.attrTween("d", arcTweenZoom(d));
d3.select("#currentlevel").text(d.name);
loadTable(d, "#datatable");
}
// Total size of all segments; we set this later, after loading the data.
var totalSize = 0;
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category20c();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("id", "container")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
svg.append("svg:circle")
.attr("r", radius)
.style("opacity", 0);
var partition = d3.layout.partition()
.sort(null)
.value(function(d) { return 1; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
// Keep track of the node that is currently being displayed as the root.
var node;
d3.json("./tst.json", function(error, root) {
console.log(root);
node = root;
var path = svg.datum(root).selectAll("path")
.data(partition.nodes)
.enter().append("path")
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.style("opacity", 1)
.on("click", click)
.on("mouseover", mousein)
.on("mouseleave", mouseout)
.each(stash);
d3.selectAll("input").on("change", function change() {
var value = this.value === "count"
? function() { return 1; }
: function(d) { return d.size; };
path
.data(partition.value(value).nodes)
.transition()
.duration(1000)
.attrTween("d", arcTweenData);
});
function click(d) {
node = d;
path.transition()
.duration(1000)
.attrTween("d", arcTweenZoom(d));
d3.select("#currentlevel").text(d.name);
loadTable(d, "#datatable");
}
// d3.select("#container").on("mouseleave", mouseleave);
initializeBreadcrumbTrail();
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
});
d3.select(self.frameElement).style("height", height + "px");
// Setup for switching data: stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
}
// When switching data: interpolate the arcs in data space.
function arcTweenData(a, i) {
var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a);
function tween(t) {
var b = oi(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
}
if (i == 0) {
// If we are on the first arc, adjust the x domain to match the root node
// at the current zoom level. (We only need to do this once.)
var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]);
return function(t) {
x.domain(xd(t));
return tween(t);
};
} else {
return tween;
}
}
// When zooming: interpolate the scales.
function arcTweenZoom(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
......@@ -115,6 +115,7 @@ trait ReportBuilder extends ToolCommand {
"js/jquery.min.js",
"js/sortable.min.js",
"js/bootstrap.min.js",
"js/gears.js",
"fonts/glyphicons-halflings-regular.woff",
"fonts/glyphicons-halflings-regular.ttf",
"fonts/glyphicons-halflings-regular.woff2"
......
#import(nl.lumc.sasc.biopet.core.summary.Summary)
#import(nl.lumc.sasc.biopet.core.report.ReportPage)
<%@ var summary: Summary %>
<%@ var rootPath: String %>
<table class="table">
<tbody>
<tr><th>Pipeline</th><td>Shiva</td></tr>
<tr><th>Version</th><td>${summary.getValue("meta", "pipeline_version")}</td></tr>
<tr><th>Last commit hash</th><td>${summary.getValue("meta", "last_commit_hash")}</td></tr>
<tr><th>Output directory</th><td>${summary.getValue("meta", "output_dir")}</td></tr>
<tr><th>Number of samples</th><td>${summary.samples.size}</td></tr>
</tbody>
</table>
<br/>
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-6">
<p>
In this web document you can find your <em>Gears</em> pipeline report.
Different categories of data can be found in the left-side menu.
Statistics per sample and library can be accessed through the top-level menu.
Some statistics for target regions can be found in the regions tab.
Futhermore, you can view all versions of software tools used by selecting <em>Versions</em> from the top menu.
</p>
<p>
<small>Brought to you by <a href="https://sasc.lumc.nl" target="_blank"><abbr
title="Sequence Analysis Support Core">SASC</abbr></a> and <a
href="https://www.lumc.nl/org/klinische-genetica/" target="_blank"><abbr title="Clinical Genetics LUMC">KG</abbr></a>,
LUMC.
</small>
</p>
</div>
</div>
\ No newline at end of file
#import(nl.lumc.sasc.biopet.core.summary.Summary)
#import(nl.lumc.sasc.biopet.core.report.ReportPage)
#import(nl.lumc.sasc.biopet.pipelines.flexiprep.FlexiprepReport)
#import(java.io.File)
<%@ var summary: Summary %>
<%@ var sampleId: Option[String] = None %>
<%@ var libId: Option[String] = None %>
<%@ var rootPath: String %>
<%@ var outputDir: File %>
<%@ var showPlot: Boolean = true %>
<%@ var showTable: Boolean = true %>
<%@ var showIntro: Boolean = true %>
<%@ var multisample: Boolean = false %>
#{
val samples = sampleId match {
case Some(sample) => List(sample.toString)
case _ => summary.samples.toList
}
val librariesCount = summary.samples.foldLeft(0)(_ + summary.libraries(_).size)
}#
<!--
Javascript required for showing the sunburst plot
//-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
<!-- Gears specific javascript -->
<script src="${rootPath}ext/js/gears.js"></script>
<h2 id='currentlevel'>Root</h2>
<div>
<span id="selection_name"></span> -
<span id="selection_size"></span> -
<span id="selection_value"></span>
</div>
<form>
<label><input type="radio" name="mode" value="size"> Size</label>
<label><input type="radio" name="mode" value="count" checked> Count</label>
</form>
<div id="sequence"></div>
<div id="datatable"></div>
#if (showIntro)
<div class="row">
<div class="col-md-1"></div>
<%--<div class="col-md-6">--%>
<p>
#if (sampleId.isDefined && libId.isDefined)
Here we show aggregated quality statistics sequencing library ${libId} for sample ${sampleId}. It shows the total number of bases used after quality control, and the total number of bases discarded during quality control. This is done for both forward and reverse reads.
#elseif (sampleId.isDefined)
Here we show aggregated quality statistics for every sequencing library for sample ${sampleId}. It shows the total number of bases used after quality control, and the total number of bases discarded during quality control. This is done for both forward and reverse reads.
#else
Here we show aggregated quality statistics for every sequencing library. It shows the total number of bases used after quality control, and the total number of bases discarded during quality control. This is done for both forward and reverse reads.
#end
</p>
</div>
</div>
#end
#if (showPlot)
#{
// FlexiprepReport.baseSummaryPlot(outputDir, "QC_Bases_R1","R1", summary, sampleId = sampleId)
// FlexiprepReport.baseSummaryPlot(outputDir, "QC_Bases_R2","R2", summary, sampleId = sampleId)
}#
<div class="panel-body">
</div>
</div>
<div class="panel-footer">
</div>
#end
package nl.lumc.sasc.biopet.pipelines.gears
import java.io.{ PrintWriter, File }
import nl.lumc.sasc.biopet.core.config.Configurable
import nl.lumc.sasc.biopet.core.report.MultisampleReportBuilder
import nl.lumc.sasc.biopet.core.report.ReportBuilderExtension
import nl.lumc.sasc.biopet.core.report.ReportPage
import nl.lumc.sasc.biopet.core.report.ReportSection
import nl.lumc.sasc.biopet.core.report._
import nl.lumc.sasc.biopet.core.summary.{ SummaryValue, Summary }
import nl.lumc.sasc.biopet.extensions.rscript.StackedBarPlot
import nl.lumc.sasc.biopet.pipelines.bammetrics.BammetricsReport
import nl.lumc.sasc.biopet.pipelines.flexiprep.FlexiprepReport
class GearsReport(val root: Configurable) extends ReportBuilderExtension {
val builder = GearsReport
override val defaultCoreMemory = 3.0
}
object GearsReport extends MultisampleReportBuilder {
// FIXME: Not yet finished
// TODO: Summary all: Add summary (sunflare plot)
// TODO: Sample specific: Add summary (sunflare plot)
// TODO: Add dusbin analysis (aggregated)
// TODO: Add alignment stats per sample for the dustbin analysis
def indexPage = {
ReportPage(
List("Samples" -> generateSamplesPage(pageArgs)) ++
Map("Files" -> filesPage,
"Versions" -> ReportPage(List(), List((
"Executables" -> ReportSection("/nl/lumc/sasc/biopet/core/report/executables.ssp"
))), Map())
),
List(
"Report" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/gears/gearsFront.ssp"),
"Alignment" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/bammetrics/alignmentSummary.ssp",
Map("sampleLevel" -> true, "showPlot" -> true, "showTable" -> false)
),
"Insert Size" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/bammetrics/insertSize.ssp",
Map("sampleLevel" -> true, "showPlot" -> true, "showTable" -> false)),
"QC reads" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/flexiprep/flexiprepReadSummary.ssp",
Map("showPlot" -> true, "showTable" -> false)),
"QC bases" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/flexiprep/flexiprepBaseSummary.ssp",
Map("showPlot" -> true, "showTable" -> false))
),
pageArgs
)
}
def filesPage = ReportPage(List(), List(
"Input fastq files" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/flexiprep/flexiprepInputfiles.ssp"),
"After QC fastq files" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/flexiprep/flexiprepOutputfiles.ssp"),
"Bam files per lib" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/mapping/outputBamfiles.ssp", Map("sampleLevel" -> false)),
"Preprocessed bam files" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/mapping/outputBamfiles.ssp",
Map("pipelineName" -> "gears", "fileTag" -> "preProcessBam"))
), Map())
def samplePage(sampleId: String, args: Map[String, Any]) = {
ReportPage(List(
"Libraries" -> generateLibraryPage(args),
"Alignment" -> BammetricsReport.bamMetricsPage(summary, Some(sampleId), None),
"Files" -> filesPage
), List(
"Alignment" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/bammetrics/alignmentSummary.ssp",
if (summary.libraries(sampleId).size > 1) Map("showPlot" -> true) else Map()),
"Preprocessing" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/bammetrics/alignmentSummary.ssp", Map("sampleLevel" -> true)),
"QC reads" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/flexiprep/flexiprepReadSummary.ssp"),
"QC bases" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/flexiprep/flexiprepBaseSummary.ssp"),
"Sunburst analysis" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/gears/gearsSunburst.ssp")
), args)
}
def libraryPage(sampleId: String, libId: String, args: Map[String, Any]) = {
ReportPage(List(
"Alignment" -> BammetricsReport.bamMetricsPage(summary, Some(sampleId), Some(libId)),
"QC" -> FlexiprepReport.flexiprepPage
), List(
"Alignment" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/bammetrics/alignmentSummary.ssp"),
"QC reads" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/flexiprep/flexiprepReadSummary.ssp"),
"QC bases" -> ReportSection("/nl/lumc/sasc/biopet/pipelines/flexiprep/flexiprepBaseSummary.ssp")
), args)
}
def reportName = "Gears :: Metagenomics Report"
}
......@@ -47,6 +47,12 @@ trait GearsTrait extends MultiSampleQScript with SummaryQScript { qscript =>
addSummaryJobs()