ideogram
Version:
Chromosome visualization with D3.js
691 lines (543 loc) • 19.5 kB
JavaScript
// Most of these tests use Mocha's async support.
// Helpful:
// - http://martinfowler.com/articles/asyncJS.html
// - https://mochajs.org/#asynchronous-code
describe("Ideogram", function() {
var config = {};
beforeEach(function() {
delete chrBands;
d3.selectAll("svg").remove();
config = {
organism: "human",
resolution: 550,
chrWidth: 10,
chrHeight: 150,
chrMargin: 10,
showChromosomeLabels: true,
orientation: "vertical"
};
});
function takeScreenshot() {
if (window.callPhantom) {
var date = new Date()
var filename = "screenshots/" + date.getTime()
console.log("Taking screenshot " + filename)
callPhantom({'screenshot': filename})
}
}
afterEach(function () {
if (this.currentTest.state == 'failed') {
takeScreenshot()
}
})
it("should have a non-body container when specified", function() {
config.container = ".small-ideogram";
var ideogram = new Ideogram(config);
assert.equal(ideogram.config.container, ".small-ideogram");
});
it("should write 'svg' element to DOM", function(done) {
function callback() {
var svg = document.getElementsByTagName("svg").length;
assert.equal(svg, 1);
done();
}
config.onLoad = callback;
var ideogram = new Ideogram(config);
// var svg = document.getElementsByTagName("svg").length;
// assert.equal(svg, 1);
});
it("should have 24 chromosomes for a human ideogram instance", function(done) {
// Tests use case from ../examples/human.html
function callback() {
var numChromosomes = Object.keys(ideogram.chromosomes["9606"]).length;
assert.equal(numChromosomes, 24);
done();
}
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it("should have 21 chromosomes for a mouse ideogram instance", function(done) {
// Tests use case from ../examples/mouse.html
function callback() {
var numChromosomes = Object.keys(ideogram.chromosomes["10090"]).length;
assert.equal(numChromosomes, 21);
done();
}
// Clears default setting from beforeEach (test artifact)
delete config.organism;
config.taxid = 10090;
config.orientation = "horizontal";
config.chromosomes = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "X", "Y"];
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it("should have 4 syntenic regions for basic homology example", function(done) {
// Tests use case from ../examples/homology_basic.html
function callback() {
var chrs = ideogram.chromosomes,
chr1 = chrs["9606"]["1"],
chr2 = chrs["9606"]["2"],
r1Band, r2Band,
r3Band, r4Band,
r5Band, r6Band,
range1, range2, range3, range4, range5, range6,
syntenicRegions = [];
r1Band = chr1.bands[2];
range1 = {
chr: chr1,
start: r1Band.bp.start,
stop: r1Band.bp.stop
};
r2Band = chr2.bands[2];
range2 = {
chr: chr2,
start: r2Band.bp.start,
stop: r2Band.bp.stop
};
// 1p11, chromosome 1 centromeric p band
r3Band = chr1.bands[22];
range3 = {
chr: chr1,
start: r3Band.bp.start,
stop: r3Band.bp.stop
};
// 2p11.1, chromosome 2 centromeric p band
r4Band = chr2.bands[13];
range4 = {
chr: chr2,
start: r4Band.bp.start,
stop: r4Band.bp.stop
};
// 1q12
r5Band = chr1.bands[24]
range5 = {
chr: chr1,
start: r5Band.bp.start,
stop: r5Band.bp.stop
};
// 2q22
r6Band = chr2.bands[24];
range6 = {
chr: chr2,
start: r6Band.bp.start,
stop: r6Band.bp.stop
};
// 1q24
r7Band = chr1.bands[29]
range7 = {
chr: chr1,
start: r7Band.bp.start,
stop: r7Band.bp.stop
};
// 2q31 - 2q33
range8 = {
chr: chr2,
start: chr2.bands[29].bp.start,
stop: chr2.bands[33].bp.stop
};
syntenicRegions.push(
{"r1": range1, "r2": range2},
{"r1": range3, "r2": range4},
{"r1": range5, "r2": range6},
{"r1": range7, "r2": range8}
);
ideogram.drawSynteny(syntenicRegions);
var numChromosomes = Object.keys(ideogram.chromosomes["9606"]).length;
assert.equal(numChromosomes, 2);
var numSyntenicRegions = document.getElementsByClassName("syntenicRegion").length;
assert.equal(numSyntenicRegions, 4);
done();
}
config.chromosomes = ["1", "2"];
config.showBandLabels = true;
config.orientation = "vertical";
config.perspective = "comparative";
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it("should have 25 syntenic regions for advanced example", function(done) {
// Tests use case from ../examples/homology_advanced.html
function callback() {
var chrs = ideogram.chromosomes,
chr1 = chrs["10090"]["1"],
chr2 = chrs["10090"]["2"],
r1Band = chr1["bands"][7],
r2Band = chr2["bands"][17],
range1, range2, range3, range4, range5, range6,
syntenicRegions = [];
range1 = {
chr: chr1,
start: r1Band.bp.start,
stop: r1Band.bp.stop
};
for (var i = 1; i < 20; i++) {
range2 = {
chr: chr2,
start: 6000000 * i,
stop: 6500000 * i
};
syntenicRegions.push({"r1": range1, "r2": range2, "color": "#F55"});
}
var range3 = {
chr: chr1,
start: 125000000,
stop: 126000000
};
range4 = {
chr: chr2,
start: 1500000 * i,
stop: 3600000 * i
};
syntenicRegions.push({"r1": range3, "r2": range4, "opacity": 0.7});
var range5 = {
chr: chr2,
start: r2Band.bp.start,
stop: r2Band.bp.stop
};
for (var i = 1; i < 6; i++) {
range6 = {
chr: chr1,
start: 120000000 + (12000000 * i),
stop: 120000000 + (8000000 * i)
};
color = "#AAF";
if (i == 5) {
color = "#DDD";
}
syntenicRegions.push({"r1": range5, "r2": range6, "color": color});
}
ideogram.drawSynteny(syntenicRegions);
var numChromosomes = Object.keys(ideogram.chromosomes["10090"]).length;
assert.equal(numChromosomes, 2);
var numSyntenicRegions = document.getElementsByClassName("syntenicRegion").length;
assert.equal(numSyntenicRegions, 25);
done();
}
config = {
taxid: 10090,
chromosomes: ["1", "2"],
chrWidth: 10,
chrHeight: 500,
chrMargin: 200,
showChromosomeLabels: true,
showBandLabels: true,
orientation: "vertical",
perspective: "comparative",
onLoad: callback
};
var ideogram = new Ideogram(config);
});
it("should have 1 syntenic region between a human and a mouse chromosome", function(done) {
// Tests use case from ../examples/homology_interspecies.html
function callback() {
// See HomoloGene entry for MTOR at
// http://www.ncbi.nlm.nih.gov/homologene/3637
// Placements for H. sapiens and M. musculus used below.
// Placements from latest annotation release in
// Human: http://www.ncbi.nlm.nih.gov/gene/2475#genomic-context
// Mouse: http://www.ncbi.nlm.nih.gov/gene/56717#genomic-context
var chrs = ideogram.chromosomes,
chr1 = chrs["9606"]["1"],
chr4 = chrs["10090"]["4"],
syntenicRegions = [];
range1 = {
chr: chr1,
start: 11106531,
stop: 11262557,
orientation: "reverse"
};
range2 = {
chr: chr4,
start: 148448582,
stop: 148557685
};
syntenicRegions.push({"r1": range1, "r2": range2});
ideogram.drawSynteny(syntenicRegions);
var numHumanChromosomes = Object.keys(ideogram.chromosomes["9606"]).length;
assert.equal(numHumanChromosomes, 1, "numHumanChromosomes");
var numMouseChromosomes = Object.keys(ideogram.chromosomes["10090"]).length;
assert.equal(numMouseChromosomes, 1, "numMouseChromosomes");
var numSyntenicRegions = document.getElementsByClassName("syntenicRegion").length;
//console.log(d3.selectAll(".syntenicRegion"));
console.log('document.getElementsByClassName("syntenicRegion")');
console.log(document.getElementsByClassName("syntenicRegion")[0][0]);
assert.equal(numSyntenicRegions, 1, "numSyntenicRegions");
done();
}
config.organism = ["human", "mouse"];
config.chromosomes = {
"human": ["1"],
"mouse": ["4"]
};
config.orientation = "vertical";
config.perspective = "comparative";
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it("should have 1000 annotations in basic annotations example", function(done) {
// Tests use case from ../examples/annotations_basic.html
function callback() {
var numAnnots = document.getElementsByClassName("annot").length;
assert.equal(numAnnots, 1000);
done();
}
config.annotationsPath = "../data/annotations/1000_virtual_snvs.json";
config.onDrawAnnots = callback;
var ideogram = new Ideogram(config);
});
it("should have 1000 annotations in overlaid annotations example", function(done) {
// Tests use case from ../examples/annotations_overlaid.html
function callback() {
var numAnnots = document.getElementsByClassName("annot").length;
assert.equal(numAnnots, 1000);
done();
}
config = {
organism: "human",
chrWidth: 10,
chrHeight: 500,
chrMargin: 5,
showChromosomeLabels: true,
annotationsPath: "../data/annotations/1000_virtual_snvs.json",
annotationsLayout: "overlay",
orientation: "horizontal",
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it("should have 1000 annotations and 5 tracks in tracks annotations example", function(done) {
// Tests use case from ../examples/annotations_tracks.html
// TODO: Add class to annots indicating track
function callback() {
var numAnnots = document.getElementsByClassName("annot").length;
assert.equal(numAnnots, 1000);
done();
}
var annotationTracks = [
{"id": "pathogenicTrack", "displayName": "Pathogenic", "color": "#F00"},
{"id": "likelyPathogenicTrack", "displayName": "Likely pathogenic", "color": "#DB9"},
{"id": "uncertainSignificanceTrack", "displayName": "Uncertain significance", "color": "#CCC"},
{"id": "likelyBenignTrack", "displayName": "Likely benign", "color": "#BD9"},
{"id": "benignTrack", "displayName": "Benign", "color": "#8D4"}
]
var config = {
taxid: 9606,
chrWidth: 8,
chrHeight: 500,
chrMargin: 10,
showChromosomeLabels: true,
annotationsPath: "../data/annotations/1000_virtual_snvs.json",
annotationTracks: annotationTracks,
annotationHeight: 2.5,
orientation: "vertical",
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it("should have 2015 annotations in histogram annotations example", function(done) {
// Tests use case from ../examples/annotations_histogram.html
// TODO: Add class to annots indicating track
function callback() {
var numAnnots = document.getElementsByClassName("annot").length;
assert.equal(numAnnots, 2015);
done();
}
var config = {
organism: "human",
chrWidth: 10,
chrHeight: 500,
chrMargin: 10,
showChromosomeLabels: true,
annotationsPath: "../data/annotations/all_human_genes.json",
annotationsLayout: "histogram",
barWidth: 3,
orientation: "vertical",
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it("should have histogram bars flush with chromosome ends", function(done) {
// Tests use case from ../examples/annotations_histogram.html
// TODO: Add class to annots indicating track
function getTerEnd(arm) {
// Helper function to get the x coordinate of the outermost
// edge of the p or q arm of chromosome 1
var ter = d3.select("." + arm + "-ter"),
terBox = ter.nodes()[0].getBBox(),
terX = terBox.x,
terWidth = terBox.width,
terEnd,
terCurve = parseInt(ter.attr("d").split(" ")[4]),
terCurveX = parseInt(ter.attr("d").split(" ")[1]),
terStroke = parseFloat(ter.style("stroke-width").slice(0, -2));
if (arm == "p") {
terEnd = terX + terWidth + terCurve + terCurveX - terStroke;
} else {
terEnd = terCurve + terCurveX - terStroke;
}
terEnd = terEnd.toFixed(2);
return terEnd;
}
function onIdeogramLoadAnnots() {
var pterEnd = getTerEnd("p"),
firstAnnotEnd = d3.selectAll("#chr1-9606 .annot").nodes()[0].getBBox().x,
qterEnd = getTerEnd("q"),
tmp = d3.selectAll("#chr1-9606 .annot").nodes(),
tmp = tmp[tmp.length - 1].getBBox(),
bump = ideogram.bump,
lastAnnotEnd = tmp.x + tmp.width;
//console.log("pterEnd - firstAnnotEnd: " + (pterEnd - firstAnnotEnd));
//console.log("qterEnd - lastAnnotEnd: " + (qterEnd - lastAnnotEnd));
assert.isBelow(pterEnd - firstAnnotEnd - bump, 1);
assert.isAbove(qterEnd - lastAnnotEnd - bump, -1);
done();
}
var config = {
organism: "human",
chrWidth: 10,
chrHeight: 500,
chrMargin: 10,
showChromosomeLabels: true,
annotationsPath: "../data/annotations/all_human_genes.json",
annotationsLayout: "histogram",
barWidth: 3,
orientation: "vertical",
onDrawAnnots: onIdeogramLoadAnnots
};
ideogram = new Ideogram(config);
});
it("should have 12 chromosomes per row in small layout example", function(done) {
// Tests use case from ../examples/layout_small.html
function callback() {
lastChrRow1Transform = d3.select("#chr12-9606").attr("transform");
firstChrRow2Transform = d3.select("#chr13-9606").attr("transform");
assert.equal(/translate/.test(lastChrRow1Transform), false);
assert.equal(/translate/.test(firstChrRow2Transform), true);
done();
}
document.getElementsByTagName("body")[0].innerHTML += '<div class="small-ideogram"></div>';
var config = {
container: ".small-ideogram",
organism: "human",
resolution: 550,
chrWidth: 10,
chrHeight: 150,
chrMargin: 10,
rows: 2,
showChromosomeLabels: true,
orientation: "vertical"
};
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it("should use GRCh37 when specified in 'assembly' parameter", function(done) {
// Tests use case from ../examples/human.html
function callback() {
var bands = ideogram.chromosomes["9606"]["1"]["bands"]
var chr1Length = bands[bands.length - 1].bp.stop;
assert.equal(chr1Length, 249250621);
done();
}
config.assembly = "GRCh37";
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it("should handle arrayed objects in 'annotations' parameter", function(done) {
// Tests use case from ../examples/human.html
function callback() {
var numAnnots = d3.selectAll(".annot").nodes().length;
assert.equal(numAnnots, 1);
done();
}
config.annotations = [{
"name": "BRCA1",
"chr": "17",
"start": 43044294,
"stop": 43125482
}];
config.onDrawAnnots = callback;
var ideogram = new Ideogram(config);
});
it("should align chr. label with thick horizontal chromosome", function(done) {
// Tests use case from ../examples/annotations_basic.html
function callback() {
var band, bandMiddle,
chrLabel, chrLabelMiddle;
band = d3.selectAll(".chromosome .band").nodes()[0].getBoundingClientRect();
chrLabel = d3.selectAll(".chromosome .chrLabel").nodes()[0].getBoundingClientRect();
bandMiddle = band.top + band.height/2;
chrLabelMiddle = chrLabel.top + chrLabel.height/2;
labelsDiff = Math.abs(bandMiddle - chrLabelMiddle);
assert.isAtMost(labelsDiff, 1);
done();
}
config = {
organism: "human",
chrHeight: 600,
chrWidth: 20,
orientation: "horizontal",
chromosomes: ["17"],
annotations: [{
"name": "BRCA1",
"chr": "17",
"start": 43044294,
"stop": 43125482
}],
annotationHeight: 6
};
config.onDrawAnnots = callback;
var ideogram = new Ideogram(config);
});
it("should align chr. label with vertical chromosome", function(done) {
// Tests use case from ../examples/human.html
function callback() {
var band, bandMiddle,
chrLabel, chrLabelMiddle;
band = d3.selectAll(".chromosome .band").nodes()[0].getBoundingClientRect();
chrLabel = d3.selectAll(".chromosome .chrLabel").nodes()[0].getBoundingClientRect();
bandMiddle = band.left + band.width/2;
chrLabelMiddle = chrLabel.left + chrLabel.width/2;
labelsDiff = Math.abs(bandMiddle - chrLabelMiddle);
assert.isAtMost(labelsDiff, 1);
done();
}
var annotationTracks = [
{"id": "pathogenicTrack", "displayName": "Pathogenic", "color": "#F00"},
{"id": "likelyPathogenicTrack", "displayName": "Likely pathogenic", "color": "#DB9"},
{"id": "uncertainSignificanceTrack", "displayName": "Uncertain significance", "color": "#CCC"},
{"id": "likelyBenignTrack", "displayName": "Likely benign", "color": "#BD9"},
{"id": "benignTrack", "displayName": "Benign", "color": "#8D4"}
]
var config = {
organism: "human",
chrWidth: 20,
chrHeight: 500,
annotationsPath: "../data/annotations/1000_virtual_snvs.json",
annotationTracks: annotationTracks,
annotationHeight: 2.5
};
config.onDrawAnnots = callback;
var ideogram = new Ideogram(config);
});
it("should align chr. label with band-labeled vertical chromosome", function(done) {
// Tests use case from ../examples/human.html
function callback() {
var band, bandMiddle,
chrLabel, chrLabelMiddle;
band = d3.select(".chromosome .band").nodes()[0].getBoundingClientRect();
chrLabel = d3.select(".chromosome .chrLabel").nodes()[0].getBoundingClientRect();
bandMiddle = band.left + band.width/2;
chrLabelMiddle = chrLabel.left + chrLabel.width/2;
labelsDiff = Math.abs(bandMiddle - chrLabelMiddle);
assert.isAtMost(labelsDiff, 1);
done();
}
var config = {
organism: "human",
showBandLabels: true,
chrHeight: 500
};
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
});