Commit 8d67efec authored by Hoogenboom, Jerry's avatar Hoogenboom, Jerry
Browse files

Redesigned Samplevis HTML visualisations

* Samplevis now features a responsive design. The options have been
  moved from the overlay into a menu bar that changes place and shape
  depending on the width and height of the viewport.
* All option labels are now clickable. When clicked, the corresponding
  option receives focus.
* The 'Save image', 'Save table', and 'Clear manually added/removed'
  links are now always visible, but change appearance when unavailable.
* When a 'No data' line is found for one or more markers, a warning is
  displayed at the bottom of the screen.
* Fixed bug that caused user-selected and user-removed alleles to get
  lost when the corresponding marker is filtered out using the marker
  name filter.
* Fixed bug in the printing stylesheet that caused conforming browsers
  to break pages between the graph and the table of a marker, instead of
  avoiding to do so.
* In HTML visualisations with embedded data, the name of the sample data
  file is now shown in the place of the file selection element.

Other Samplevis fixes and improvements:
* Added option to show sequences that are filtered from the graphs as a
  single 'Other sequences' aggregate entry per marker (default: on).
* For alleles that end up at a negative read count after correction now
  have a strand balance line in the 'overlap' portion of their bar only.
* The strand bias mark is now correctly positioned when using the square
  root scale.

Improved:
* HTML visualisations with embedded data will now use a proper filename
  for the 'Save graph' and 'Save table' options.
parent 2d1fffde
......@@ -92,8 +92,6 @@ _DEF_DATA_FILENAME = "data.csv"
_PAT_TITLE = re.compile("<title>\s*(.*?)\s*"
"</title>", flags=re.DOTALL|re.IGNORECASE)
_PAT_LIBRARIES = re.compile("<!--\s*BEGIN_LIBRARIES\s*-->\s*(.*?)\s*"
"<!--\s*END_LIBRARIES\s*-->", flags=re.DOTALL)
_PAT_LOAD_SCRIPT = re.compile("<!--\s*BEGIN_LOAD_SCRIPT\s*-->\s*(.*?)\s*"
......@@ -134,7 +132,8 @@ def set_axis_scale(spec, scalename, value):
def create_visualisation(vistype, infile, outfile, vega, online, tidy, min_abs,
min_pct_of_max, min_pct_of_sum, min_per_strand,
bias_threshold, bar_width, padding, marker, width,
height, log_scale, repeat_unit, no_alldata, title):
height, log_scale, repeat_unit, no_alldata,
no_aggregate, title):
# Get graph spec.
spec = json.load(resource_stream(
"fdstools", "vis/%svis/%svis.json" % (vistype, vistype)))
......@@ -172,6 +171,7 @@ def create_visualisation(vistype, infile, outfile, vega, online, tidy, min_abs,
set_signal_value(spec, "orientation_threshold", min_per_strand)
set_signal_value(spec, "bias_threshold", bias_threshold)
set_signal_value(spec, "amplitude_markerpct_threshold", min_pct_of_sum)
set_signal_value(spec, "show_other", False if no_aggregate else True)
# Apply axis scale settings.
if vistype != "stuttermodel" and vistype != "allele":
......@@ -182,6 +182,15 @@ def create_visualisation(vistype, infile, outfile, vega, online, tidy, min_abs,
else:
set_axis_scale(spec, "x", "log")
# Add title if available.
if title is None and infile is not None and infile != sys.stdin:
try:
title = os.path.splitext(os.path.basename(infile.name))[0]
except AttributeError:
pass
if title:
spec["data"][0]["fdstools_filename"] = title;
# Stringify spec.
if tidy:
spec = json.dumps(spec, indent=2, separators=(",", ": "))
......@@ -219,17 +228,6 @@ def create_visualisation(vistype, infile, outfile, vega, online, tidy, min_abs,
parts.append(html[match.end(1):])
html = "".join(parts)
if title is None and infile is not None and infile != sys.stdin:
try:
title = os.path.splitext(os.path.basename(infile.name))[0]
except AttributeError:
pass
if title:
match = _PAT_TITLE.search(html)
if match:
html = "".join([
html[:match.start(1)], title, " - ", html[match.start(1):]])
outfile.write(html)
#create_visualisation
......@@ -329,6 +327,9 @@ def add_arguments(parser):
"(default: %(default)s)")
visgroup.add_argument('-A', '--no-alldata', action="store_true",
help="[stuttermodel] if specified, show only marker-specific fits")
visgroup.add_argument('-a', '--no-aggregate', action="store_true",
help="[sample] if specified, do not replace filtered sequences with a"
"per-marker aggregate 'Other sequences' entry")
#add_arguments
......@@ -356,5 +357,5 @@ def run(args):
args.min_per_strand, args.bias_threshold,
args.bar_width, args.padding, args.marker, args.width,
args.height, args.log_scale, args.repeat_unit,
args.no_alldata, args.title)
args.no_alldata, args.no_aggregate, args.title)
#run
......@@ -185,6 +185,16 @@
graph.renderer(value).update();
}
function setFileName(value){
if(!value)
value = "graph";
fileName = value;
if(value == "graph")
document.title = "Allele Visualisation - FDSTools";
else
document.title = value + " - Allele Visualisation - FDSTools";
}
//Load the data (input is a fileList object; only the first file is loaded).
var currentlyLoadedFile = "no/file/loaded";
function loadDataset(fileList){
......@@ -194,10 +204,9 @@
var reader = new FileReader();
reader.onload = function(e){
if(fileList && fileList.length && fileList[0].name)
fileName = fileList[0].name.substr(0, fileList[0].name.lastIndexOf("."));
setFileName(fileList[0].name.substr(0, fileList[0].name.lastIndexOf(".")));
else
fileName = "graph";
document.title = fileName + " - Allele Visualisation - FDSTools";
setFileName(false);
graph_spec.data[0].values = reader.result;
parse();
};
......@@ -254,6 +263,8 @@
loadDataset(evt.dataTransfer.files);
}, false);
}
else if(graph_spec.data[0].fdstools_filename)
setFileName(graph_spec.data[0].fdstools_filename);
//Update graph when rendering mode or axis scale is changed.
document.getElementById("renderCanvas").addEventListener('change', function(){
......
......@@ -208,6 +208,16 @@
graph.renderer(value).update();
}
function setFileName(value){
if(!value)
value = "graph";
fileName = value;
if(value == "graph")
document.title = "Background Noise Visualisation - FDSTools";
else
document.title = value + " - Background Noise Visualisation - FDSTools";
}
//Load the data (input is a fileList object; only the first file is loaded).
var currentlyLoadedFile = "no/file/loaded";
function loadDataset(fileList){
......@@ -217,10 +227,9 @@
var reader = new FileReader();
reader.onload = function(e){
if(fileList && fileList.length && fileList[0].name)
fileName = fileList[0].name.substr(0, fileList[0].name.lastIndexOf("."));
setFileName(fileList[0].name.substr(0, fileList[0].name.lastIndexOf(".")));
else
fileName = "graph";
document.title = fileName + " - Background Noise Visualisation - FDSTools";
setFileName(false);
graph_spec.data[0].values = reader.result;
parse();
};
......@@ -277,6 +286,8 @@
loadDataset(evt.dataTransfer.files);
}, false);
}
else if(graph_spec.data[0].fdstools_filename)
setFileName(graph_spec.data[0].fdstools_filename);
//Update graph when rendering mode or axis scale is changed.
document.getElementById("renderCanvas").addEventListener('change', function(){
......
......@@ -205,6 +205,16 @@
graph.renderer(value).update();
}
function setFileName(value){
if(!value)
value = "graph";
fileName = value;
if(value == "graph")
document.title = "Background Noise Profile Visualisation - FDSTools";
else
document.title = value + " - Background Noise Profile Visualisation - FDSTools";
}
//Load the data (input is a fileList object; only the first file is loaded).
var currentlyLoadedFile = "no/file/loaded";
function loadDataset(fileList){
......@@ -214,10 +224,9 @@
var reader = new FileReader();
reader.onload = function(e){
if(fileList && fileList.length && fileList[0].name)
fileName = fileList[0].name.substr(0, fileList[0].name.lastIndexOf("."));
setFileName(fileList[0].name.substr(0, fileList[0].name.lastIndexOf(".")));
else
fileName = "graph";
document.title = fileName + " - Background Noise Profile Visualisation - FDSTools";
setFileName(false);
graph_spec.data[0].values = reader.result;
parse();
};
......@@ -274,6 +283,8 @@
loadDataset(evt.dataTransfer.files);
}, false);
}
else if(graph_spec.data[0].fdstools_filename)
setFileName(graph_spec.data[0].fdstools_filename);
//Update graph when rendering mode or axis scale is changed.
document.getElementById("renderCanvas").addEventListener('change', function(){
......
This diff is collapsed.
......@@ -22,6 +22,10 @@
"name": "show_negative",
"init": true
},
{
"name": "show_other",
"init": true
},
{
"name": "bias_threshold",
"init": 25
......@@ -47,7 +51,7 @@
"name": "clickedAllele",
"verbose": true,
"streams": [
{"type": "@alleleSelector:click", "expr": "datum.thedatum"}
{"type": "@alleleSelector:click[datum.thedatum]", "expr": "datum.thedatum"}
]
}
],
......@@ -70,6 +74,10 @@
}
},
"transform": [
{
"type": "filter",
"test": "test(marker_regex, datum.marker)"
},
{
"type": "formula",
"field": "forward_corr",
......@@ -114,13 +122,9 @@
]
},
{
"name": "annotated",
"name": "preannotated",
"source": "raw",
"transform": [
{
"type": "filter",
"test": "test(marker_regex, datum.marker)"
},
{
"type": "lookup",
"on": "aggr",
......@@ -133,11 +137,23 @@
"type": "formula",
"field": "pct_of_max",
"expr": "datum.aggr.max_total_added? (datum.total_added / datum.aggr.max_total_added * 100) : 100"
}, {
},
{
"type": "formula",
"field": "pct_of_sum",
"expr": "datum.aggr.sum_total_added? (datum.total_added / datum.aggr.sum_total_added * 100) : 100"
},
{
"type": "formula",
"field": "markersequence",
"expr": "datum.marker + '\t' + datum.sequence"
}
]
},
{
"name": "annotated",
"source": "preannotated",
"transform": [
{
"type": "formula",
"field": "shared",
......@@ -155,23 +171,28 @@
},
{
"type": "formula",
"field": "corr_increased",
"expr": "max(datum.corr_shared, datum.total_added)"
"field": "forwardpct",
"expr": "datum.total_added? (100.0 / datum.total_added * datum.forward_added) : 50"
},
{
"type": "formula",
"field": "forwardpct",
"expr": "datum.total_added? (100.0 / datum.total_added * datum.forward_added) : 50"
"field": "minimum",
"expr": "min(0, datum.total_corr)"
},
{
"type": "formula",
"field": "xlow",
"expr": "min(0, datum.total_corr*1.05)"
"expr": "datum.minimum*1.05"
},
{
"type": "formula",
"field": "maximum",
"expr": "max(datum.total, datum.total_added)"
},
{
"type": "formula",
"field": "xhigh",
"expr": "max(datum.total*1.05, datum.total_added*1.05)"
"expr": "datum.maximum*1.05"
},
{
"type": "formula",
......@@ -187,11 +208,94 @@
},
{
"name": "table",
"source": "annotated",
"source": "preannotated",
"transform": [
{
"type": "formula",
"field": "sequence",
"expr": "((show_negative? abs(datum.total_added) : datum.total_added) >= amplitude_threshold && (show_negative? abs(datum.pct_of_max) : datum.pct_of_max) >= amplitude_pct_threshold && (show_negative? abs(datum.pct_of_sum) : datum.pct_of_sum) >= amplitude_markerpct_threshold && min(show_negative? abs(datum.forward_added) : datum.forward_added, show_negative? abs(datum.reverse_added) : datum.reverse_added) >= orientation_threshold)? datum.sequence : 'Other sequences'"
},
{
"type": "filter",
"test": "(show_negative? abs(datum.total_added) : datum.total_added) >= amplitude_threshold && (show_negative? abs(datum.pct_of_max) : datum.pct_of_max) >= amplitude_pct_threshold && (show_negative? abs(datum.pct_of_sum) : datum.pct_of_sum) >= amplitude_markerpct_threshold && min(show_negative? abs(datum.forward_added) : datum.forward_added, show_negative? abs(datum.reverse_added) : datum.reverse_added) >= orientation_threshold"
"test": "show_other || datum.sequence != 'Other sequences'"
},
{
"type": "aggregate",
"groupby": ["marker", "sequence"],
"summarize": [
{"field": "forward", "ops": ["sum"], "as": ["forward"]},
{"field": "reverse", "ops": ["sum"], "as": ["reverse"]},
{"field": "total", "ops": ["sum"], "as": ["total"]},
{"field": "forward_noise", "ops": ["sum"], "as": ["forward_noise"]},
{"field": "reverse_noise", "ops": ["sum"], "as": ["reverse_noise"]},
{"field": "total_noise", "ops": ["sum"], "as": ["total_noise"]},
{"field": "forward_add", "ops": ["sum"], "as": ["forward_add"]},
{"field": "reverse_add", "ops": ["sum"], "as": ["reverse_add"]},
{"field": "total_add", "ops": ["sum"], "as": ["total_add"]},
{"field": "forward_corr", "ops": ["sum"], "as": ["forward_corr"]},
{"field": "reverse_corr", "ops": ["sum"], "as": ["reverse_corr"]},
{"field": "total_corr", "ops": ["sum"], "as": ["total_corr"]},
{"field": "forward_added", "ops": ["sum"], "as": ["forward_added"]},
{"field": "reverse_added", "ops": ["sum"], "as": ["reverse_added"]},
{"field": "total_added", "ops": ["sum"], "as": ["total_added"]}
]
},
{
"type": "formula",
"field": "shared",
"expr": "max(0, datum.total_corr)"
},
{
"type": "formula",
"field": "corr_shared",
"expr": "min(datum.total, datum.total_added)"
},
{
"type": "formula",
"field": "corr_decreased",
"expr": "max(datum.total, datum.corr_shared)"
},
{
"type": "formula",
"field": "forwardpct",
"expr": "datum.total_added? (100.0 / datum.total_added * datum.forward_added) : 50"
},
{
"type": "formula",
"field": "minimum",
"expr": "min(0, datum.total_corr)"
},
{
"type": "formula",
"field": "xlow",
"expr": "datum.minimum*1.05"
},
{
"type": "formula",
"field": "maximum",
"expr": "max(datum.total, datum.total_added)"
},
{
"type": "formula",
"field": "xhigh",
"expr": "datum.maximum*1.05"
},
{
"type": "formula",
"field": "biasmark",
"expr": "50-abs(datum.forwardpct-50) < bias_threshold? '\u2605' : ''"
},
{
"type": "formula",
"field": "markersequence",
"expr": "datum.marker + '\t' + datum.sequence"
},
{
"type": "lookup",
"on": "annotated",
"onKey": "markersequence",
"keys": ["markersequence"],
"as": ["thedatum"]
},
{
"type": "sort",
......@@ -485,7 +589,7 @@
"properties": {
"update": {
"x": {"scale": "x", "field": "corr_shared"},
"x2": {"scale": "x", "field": "corr_increased"},
"x2": {"scale": "x", "field": "total_added"},
"y": {"scale": "y", "field": "sequence", "offset": 1},
"height": {"scale": "y", "band": true, "offset": -2},
"fill": {"scale": "c", "value": "Recovered reads"},
......@@ -520,7 +624,7 @@
"type": "rule",
"properties": {
"update": {
"x": {"scale": "x", "value": 0},
"x": {"scale": "x", "field": "minimum"},
"x2": {"scale": "x", "field": "total_added"},
"y": {"scale": "balance", "field": "forwardpct"},
"stroke": {"scale": "b", "field": "biasmark"},
......@@ -532,7 +636,7 @@
"type": "text",
"properties": {
"update": {
"x": {"scale": "x", "field": "xhigh", "mult": 0.952381, "offset": 5},
"x": {"scale": "x", "field": "maximum", "offset": 5},
"y": {"scale": "balance", "value": 50},
"baseline": {"value": "middle"},
"fill": {"scale": "b", "field": "biasmark"},
......
<svg width="260" height="260" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffcc00" stroke="#000000" stroke-width="7" stroke-miterlimit="4" d="m13.684242,210.960907c-1.631832,3.141983 -3.555143,6.079559 -3.555323,9.527863l0.016121,1.441208c0,7.44249 5.83098,11.941727 12.534727,11.941727l216.607708,0c6.704285,0 12.478119,-5.577423 12.478119,-13.019913l-0.071167,-1.447601c0,-3.448334 -1.696655,-6.442673 -3.554779,-9.527908l-107.274673,-192.270634c-4.739578,-5.261784 -12.423538,-5.261784 -17.163849,0.00071l-110.016884,193.354549z"/>
<path fill="#000000" d="m150.597687,191.75415a17.091516,17.077017 0 1 1-34.183014,0a17.091516,17.077017 0 1 134.183014,0z"/>
<path fill="#000000" d="m133.394882,73.106613c6.7146,0 17.135437,5.345276 17.135437,11.984901l-5.015228,69.004005c0,6.639648 -5.405609,11.984924 -12.120209,11.984924c-6.7146,0 -12.120193,-5.345276 -12.120193,-11.984924l-5.851135,-69.004005c0,-6.639626 11.256729,-11.984901 17.971329,-11.984901z"/>
</svg>
\ No newline at end of file
......@@ -196,6 +196,16 @@
graph.renderer(value).update();
}
function setFileName(value){
if(!value)
value = "graph";
fileName = value;
if(value == "graph")
document.title = "Stutter Model Visualisation - FDSTools";
else
document.title = value + " - Stutter Model Visualisation - FDSTools";
}
//Load the data (input is a fileList object; only the first file is loaded).
var currentlyLoadedFile = "no/file/loaded";
function loadDataset(fileList){
......@@ -205,10 +215,9 @@
var reader = new FileReader();
reader.onload = function(e){
if(fileList && fileList.length && fileList[0].name)
fileName = fileList[0].name.substr(0, fileList[0].name.lastIndexOf("."));
setFileName(fileList[0].name.substr(0, fileList[0].name.lastIndexOf(".")));
else
fileName = "graph";
document.title = fileName + " - Stutter Model Visualisation - FDSTools";
setFileName(false);
graph_spec.data[0].values = reader.result;
parse();
};
......@@ -265,6 +274,8 @@
loadDataset(evt.dataTransfer.files);
}, false);
}
else if(graph_spec.data[0].fdstools_filename)
setFileName(graph_spec.data[0].fdstools_filename);
//Update graph when rendering mode is changed.
document.getElementById("renderCanvas").addEventListener('change', function(){
......
To-do:
* TRY: BGCorrect edits to get rid of overcorrection of singletons:
* Round final output to nearest integer; halves away from zero.
* Clip deviations of less than 1 read to zero while iterating.
* Both.
* Samplevis:
* Re-layout, inspiration:
http://matthewjamestaylor.com/blog/perfect-multi-column-liquid-layouts
* Replace the alert for 'No data' markers with a message on the page.
* Allow for heavily corrected sequences to remain visible in the graphs.
* Make sure markers for which no sequences meet the filtering criteria are
still visible.
* Add option to truncate long allele names.
* Sort STR alleles by length by default.
* Option to adjust the sorting.
* Option to choose complete table download.
* When we have them, add default values to table filtering (for reference).
* Some of the media query breakpoints overlap, fix this.
* Perhaps it is desirable to be able to request a list of 'Other sequences'.
* Additions needed for publication:
* [If not too difficult to implement] BGEstimate should start with homozygous
samples and add heterozygous samples later to optimise correction.
......@@ -24,11 +25,12 @@ To-do:
* Add visualisation with all markers in one graph ("samplesummaryvis"?).
* Allow loading multiple files into HTML visualisations and provide prev/next
buttons to browse them.
* Visualisations in IE10:
* Printing striped table rows does not seem to work. (But pagebreaks work
better than Chrome!!)
* Samplevis HTML visualisations in IE11:
* Printing striped table rows does not seem to work, though this might be an
NFI-specific issue.
* Tables are not perfectly aligned with the graphs (graphs render slightly
differently). Firefox: same thing (and not identical to IE either).
differently). Firefox is just 1px off (using em units for positioning now).
* When printing, IE11 respects the pagebreak hints. Chrome and FF are bugged!
* [Known bug]: pattern_longest_match does not give the longest match if a
shorter match is possible and found earlier at the same position.
* Add tool that takes a configuration file and runs a pipeline of other tools.
......@@ -48,6 +50,8 @@ To-do:
(TODO: Write this list)
Open Vega issues:
* Lookup transform only takes simple field names for the onKey parameter.
https://github.com/vega/vega/issues/526
* Sorting is broken.
https://github.com/vega/vega/issues/509
* Feature request: Id-based refs for Force transform's source and target.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment