ideogram
Version:
Chromosome visualization with D3.js
1,700 lines (1,416 loc) • 55.3 kB
JavaScript
// Most of these tests use Mocha's async support.
// Helpful:
// - http://martinfowler.com/articles/asyncJS.html
// - https://mochajs.org/#asynchronous-code
// innerHTML doesn't work for SVG in PhantomJS. This is a workaround.
function getSvgText(selector) {
var svgText =
new XMLSerializer()
.serializeToString(
document.querySelector(selector)
)
.split('>')[1]
.split('</')[0];
return svgText;
}
describe('Ideogram', function() {
var config = {};
d3 = Ideogram.d3;
beforeEach(function() {
delete window.chrBands;
d3.selectAll('svg').remove();
config = {
organism: 'human',
chrWidth: 10,
chrHeight: 150,
chrMargin: 10,
showChromosomeLabels: true,
orientation: 'vertical',
dataDir: '/dist/data/bands/native/'
};
});
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/vanilla/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/vanilla/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.onLoad = callback;
var ideogram = new Ideogram(config);
});
it('should have 4 syntenic regions for basic homology example', function(done) {
// Tests use case from ../examples/vanilla/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/vanilla/homology-advanced.html
function callback() {
var chrs = ideogram.chromosomes,
chr1 = chrs['10090']['1'],
chr2 = chrs['10090']['2'],
r1Band = chr1.bands[8],
r2Band = chr2.bands[18],
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);
var srID = '#chr1-10090_54516053_55989459___chr2-10090_114000000_123500000';
var otherSrID = '#chr1-10090_54516053_55989459___chr2-10090_108000000_117000000';
var sr = d3.select(srID);
var otherSr = d3.select(otherSrID);
sr.dispatch('mouseover');
var otherSrIsTranslucent = /ghost/.test(otherSr.nodes()[0].classList.value);
assert.equal(otherSrIsTranslucent, true);
sr.dispatch('mouseout');
sr.dispatch('click');
var otherSrIsHidden = /hidden/.test(otherSr.nodes()[0].classList.value);
assert.equal(otherSrIsHidden, true);
done();
}
config = {
taxid: 10090,
chromosomes: ['1', '2'],
chrWidth: 10,
chrHeight: 500,
chrMargin: 200,
showChromosomeLabels: true,
showBandLabels: true,
orientation: 'vertical',
perspective: 'comparative',
dataDir: '/dist/data/bands/native/',
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/vanilla/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, range2;
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')[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/vanilla/annotations-basic.html
function callback() {
var numAnnots = document.getElementsByClassName('annot').length;
assert.equal(numAnnots, 1000);
done();
}
config.annotationsPath = '../dist/data/annotations/1000_virtual_snvs.json';
config.annotationsNumTracks = 3;
config.onDrawAnnots = callback;
var ideogram = new Ideogram(config);
});
it('should have 48 annotations in overlaid annotations example', function(done) {
// Tests use case from old ../examples/vanilla/annotations-overlaid.html
function callback() {
var numAnnots = document.getElementsByClassName('annot').length;
assert.equal(numAnnots, 48);
done();
}
config = {
organism: 'human',
chrWidth: 10,
chrHeight: 500,
chrMargin: 5,
showChromosomeLabels: true,
annotationsPath: '../dist/data/annotations/1000_virtual_snvs.json',
annotationsLayout: 'overlay',
orientation: 'horizontal',
dataDir: '/dist/data/bands/native/',
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have 10 spanning overlaid annotations in proper chromosomes', function(done) {
// Tests:
// * https://github.com/eweitz/ideogram/issues/65
// * https://github.com/eweitz/ideogram/issues/66
function callback() {
// Correct number?
var numAnnots = document.querySelectorAll('.annot').length;
assert.equal(numAnnots, 10);
// Correct order?
var numChr20Annots = document.querySelectorAll('#chr20-9606 .annot').length;
assert.equal(numChr20Annots, 1);
// Spanning, not point?
var chr1Annot = document.querySelectorAll('#chr1-9606 .annot')[0];
var chr1AnnotWidth = chr1Annot.getBBox().width;
assert.isAbove(chr1AnnotWidth, 3);
done();
}
config = {
organism: 'human',
annotationsPath: '../dist/data/annotations/10_virtual_cnvs.json',
annotationsLayout: 'overlay',
orientation: 'horizontal',
dataDir: '/dist/data/bands/native/',
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/vanilla/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: '../dist/data/annotations/1000_virtual_snvs.json',
annotationTracks: annotationTracks,
annotationHeight: 2.5,
orientation: 'vertical',
dataDir: '/dist/data/bands/native/',
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have properly scaled annotations after rotating', function(done) {
// Tests use case from ../examples/vanilla/annotations-tracks.html
function callback() {
var annot, annotBox;
annot = document.getElementsByClassName('annot')[3];
annotBox = annot.getBoundingClientRect();
assert.isBelow(Math.abs(annotBox.x - 75), 2);
assert.isBelow(Math.abs(annotBox.y - 510), 2);
assert.isBelow(Math.abs(annotBox.height - 14), 2);
assert.isBelow(Math.abs(annotBox.right - 89), 2);
assert.isBelow(Math.abs(annotBox.bottom - 523), 2);
assert.isBelow(Math.abs(annotBox.left - 75), 2);
done();
}
// Click chromosome 1 after it's loaded and had time to draw annotations.
function loadCallback() {
setTimeout(function() {
d3.select('#chr1-9606').dispatch('click');
}, 200);
}
var annotationTracks = [
{id: 'pathogenicTrack', displayName: 'Pathogenic', color: '#F00'},
{id: 'uncertainSignificanceTrack', displayName: 'Uncertain significance', color: '#CCC'},
{id: 'benignTrack', displayName: 'Benign', color: '#8D4'}
];
var config = {
organism: 'human',
chrWidth: 8,
showChromosomeLabels: true,
annotationsPath: '../dist/data/annotations/1000_virtual_snvs.json',
annotationTracks: annotationTracks,
annotHeight: 3.5,
dataDir: '/dist/data/bands/native/',
onLoad: loadCallback,
onDidRotate: callback
};
ideogram = new Ideogram(config);
});
it('should have filterable annotations', function(done) {
// Tests use case from ../examples/vanilla/annotations-histogram.html
var priorBody = document.querySelector('body').innerHTML;
var firstRun = true;
var numAnnotsInFirstBar;
// Ensure the first histogram bar represents
// 110 annotations before filtering, and
// 2 annotations after filtering
function callback() {
numAnnotsInFirstBar = ideogram.bars[0].annots[0].count;
if (firstRun) {
assert.equal(numAnnotsInFirstBar, 110);
firstRun = false;
document.querySelector('#filter_expression-level_extremely-high').click();
return;
}
assert.equal(numAnnotsInFirstBar, 2);
document.querySelector('body').innerHTML = priorBody;
done();
}
var htmlScaffolding = '<div id="container"></div>' +
'<ul id="expression-level">' +
'Expression level' +
'<li>' +
'<label for="filter_expression-level_extremely-high">' +
'<input type="checkbox" id="filter_expression-level_extremely-high">Extremely high</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_expression-level_very-high">' +
'<input type="checkbox" id="filter_expression-level_very-high">Very high</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_expression-level_high">' +
'<input type="checkbox" id="filter_expression-level_high">High</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_expression-level_moderately-high">' +
'<input type="checkbox" id="filter_expression-level_moderately-high">Moderately high</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_expression-level_moderate">' +
'<input type="checkbox" id="filter_expression-level_moderate">Moderate</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_expression-level_low">' +
'<input type="checkbox" id="filter_expression-level_low">Low</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_expression-level_very-low">' +
'<input type="checkbox" id="filter_expression-level_very-low">Very low</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'</ul>' +
'<ul id="gene-type">' +
'Gene type' +
'<li>' +
'<label for="filter_gene-type_mrna">' +
'<input type="checkbox" id="filter_gene-type_mrna">mRNA</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_gene-type_misc-rna">' +
'<input type="checkbox" id="filter_gene-type_misc-rna">misc_RNA</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_gene-type_mirna">' +
'<input type="checkbox" id="filter_gene-type_mirna">miRNA</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_gene-type_trna">' +
'<input type="checkbox" id="filter_gene-type_trna">tRNA</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'<li>' +
'<label for="filter_gene-type_lncrna">' +
'<input type="checkbox" id="filter_gene-type_lncrna">lncRNA</input>' +
'<span class="count"></span>' +
'</label>' +
'</li>' +
'</ul>';
document.querySelector('body').innerHTML = htmlScaffolding;
var filterMap = {
'expression-level': {
'extremely-high': 7,
'very-high': 6,
'high': 5,
'moderately-high': 4,
'moderate': 3,
'low': 2,
'very-low': 1
},
'gene-type': {
'mrna': 1,
'misc-rna': 2,
'mirna': 3,
'trna': 4,
'lncrna': 5
},
'tissue-type': {
'cerebral-cortex': 1,
'heart': 2,
'liver': 3,
'skin': 4,
'skeletal-muscle': 5
}
};
d3.selectAll('input').on('click', function() {
var tmp, checkedFilter, checkedFilters, i, facet, counts, count,
filterID, key,
selections = {};
checkedFilters = d3.selectAll('input:checked').nodes();
for (i = 0; i < checkedFilters.length; i++) {
tmp = checkedFilters[i].id.split('_');
facet = tmp[1];
checkedFilter = tmp[2];
filterID = filterMap[facet][checkedFilter];
if (facet in selections === false) {
selections[facet] = {};
}
selections[facet][filterID] = 1;
}
counts = ideogram.filterAnnots(selections);
for (facet in counts) {
for (i = 0; i < counts[facet].length; i++) {
count = counts[facet][i];
key = count.key - 1;
value = '(' + count.value + ')';
// document.querySelectorAll('#' + facet + ' .count')[key].innerHTML = value;
}
}
});
var config = {
container: '#container',
orientation: 'vertical',
organism: 'human',
assembly: 'GRCh37',
chrHeight: 275,
annotationsPath: '/dist/data/annotations/SRR562646.json',
dataDir: '/dist/data/bands/native/',
annotationsLayout: 'histogram',
barWidth: 3,
filterable: true,
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have filterable tracks in track-filters example', function(done) {
// Tests use case from ../examples/vanilla/annotations-track-filters.html
var firstRun = true;
function callback() {
if (firstRun) {
firstRun = false;
} else {
return;
}
var numAnnots = document.getElementsByClassName('annot').length;
assert.equal(numAnnots, 696);
// Filters tracks to show only 4th and 5th track (of 20)
ideogram.updateDisplayedTracks([4, 5]);
numAnnots = document.getElementsByClassName('annot').length;
assert.equal(numAnnots, 620);
done();
}
var config = {
organism: 'human',
annotationsPath: '../dist/data/annotations/9_tracks_virtual_snvs.json',
dataDir: '/dist/data/bands/native/',
annotationsNumTracks: 3,
annotationsDisplayedTracks: [1, 5, 9],
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have 2015 annotations in histogram annotations example', function(done) {
// Tests use case from ../examples/vanilla/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: '../dist/data/annotations/all_human_genes.json',
annotationsLayout: 'histogram',
barWidth: 3,
orientation: 'vertical',
dataDir: '/dist/data/bands/native/',
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have narrow rectangles as custom annotations shape', function(done) {
// Tests use case from ../examples/vanilla/annotations-tracks.html
function callback() {
var annot, box;
annot = document.querySelector('#chr6-9606 > g:nth-child(7)');
box = annot.getBBox();
assert.equal(box.height, 7);
assert.equal(box.width, 1.75);
done();
}
var annotHeight = 3.5;
var shape =
'm0,0 l 0 ' + (2 * annotHeight) +
'l ' + annotHeight/2 + ' 0' +
'l 0 -' + (2 * annotHeight) + 'z';
var annotationTracks = [
{id: 'pathogenicTrack', displayName: 'Pathogenic', color: '#F00', shape: shape},
{id: 'uncertainSignificanceTrack', displayName: 'Uncertain significance', color: '#CCC', shape: shape},
{id: 'benignTrack', displayName: 'Benign', color: '#8D4', shape: shape},
];
var config = {
organism: 'human',
orientation: 'vertical',
chrWidth: 8,
annotationsPath: '../dist/data/annotations/1000_virtual_snvs.json',
annotationTracks: annotationTracks,
annotationHeight: annotHeight,
dataDir: '/dist/data/bands/native/',
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have 114 annotations for BED file at remote URL', function(done) {
// Tests use case from ../examples/vanilla/annotations-file-url.html
function callback() {
var numAnnots = document.getElementsByClassName('annot').length;
assert.equal(numAnnots, 114);
done();
}
var config = {
organism: 'human',
assembly: 'GRCh37',
annotationsPath: 'https://raw.githubusercontent.com/NCBI-Hackathons/Scan2CNV/master/files/201113910010_R08C02.PennCnvOut.bed',
dataDir: '/dist/data/bands/native/',
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have 10 annotations for native annots at remote URL', function(done) {
// Tests use case from ../examples/vanilla/annotations-file-url.html
function callback() {
var numAnnots = document.getElementsByClassName('annot').length;
assert.equal(numAnnots, 10);
done();
}
var config = {
organism: 'human',
chrHeight: 300,
chrMargin: 2,
annotationsPath: 'https://unpkg.com/ideogram@0.15.0/dist/data/annotations/10_virtual_cnvs.json',
annotationsLayout: 'overlay',
orientation: 'horizontal',
dataDir: '/dist/data/bands/native/',
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have two heatmap tracks for each chromosome', function(done) {
// Tests use case from ../examples/vanilla/annotations-heatmap.html
function callback() {
var numHeatmaps = document.querySelectorAll('canvas').length;
var chr1HeatmapTrackTwo = document.querySelectorAll('canvas#chr1-9606-canvas-1').length;
assert.equal(numHeatmaps, 48);
assert.equal(chr1HeatmapTrackTwo, 1);
done();
}
document.getElementsByTagName('body')[0].innerHTML +=
'<div id="container"></div>';
var annotationTracks = [
{id: 'expressionLevelTrack', displayName: 'Expression level'},
{id: 'geneTypeTrack', displayName: 'Gene type'},
];
var config = {
container: '#container',
organism: 'human',
assembly: 'GRCh37',
chrHeight: 275,
annotationsPath: '../dist/data/annotations/SRR562646.json',
annotationsLayout: 'heatmap',
heatmaps: [
{
key: 'expression-level',
thresholds: [['0', '#AAA'], ['3', '#88F'], ['+', '#F33']]
},
{
key: 'gene-type',
thresholds: [['0', '#00F'], ['1', '#0AF'], ['2', '#AAA'], ['3', '#FA0'], ['4', '#F00']]
}
],
annotationTracks: annotationTracks,
dataDir: '/dist/data/bands/native/',
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should show tooltip upon hovering over annotation ', function(done) {
// Tests use case from ../examples/vanilla/annotations-basic.html
function callback() {
d3.select('.annot path').dispatch('mouseover');
var content = d3.select('.tooltip').html();
assert.equal(content, 'BRCA1<br>chr17:43,044,294-43,125,482');
d3.select('.annot path').dispatch('mouseout');
done();
}
var config = {
organism: 'human',
chromosome: '17',
chrHeight: 600,
orientation: 'horizontal',
annotations: [{
name: 'BRCA1',
chr: '17',
start: 43044294,
stop: 43125482
}],
dataDir: '/dist/data/bands/native/',
onDrawAnnots: callback
};
ideogram = new Ideogram(config);
});
it('should have histogram bars roughly flush with chromosome ends', function(done) {
// Tests use case from ../examples/vanilla/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 armIndex = (arm === 'p') ? 1 : 2,
ter = d3.selectAll('.chromosome-border path:nth-child(' + armIndex + ')'),
terEnd,
inst = ter.attr('d').split(' '), // Path instructions in description ('d')
terCurve = parseInt(inst[4].replace('Q', '').split(',')[0]),
terCurveX = parseInt(inst[0].replace('M', '').split(',')[0]),
terStroke = parseFloat(ter.style("stroke-width").slice(0, -2));
if (arm === 'p') {
terEnd = terCurve;
} 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(),
lastAnnotEnd = tmp.x + tmp.width;
// console.log("pterEnd - firstAnnotEnd: " + (pterEnd - firstAnnotEnd));
// console.log("qterEnd - lastAnnotEnd: " + (qterEnd - lastAnnotEnd));
assert.isBelow(pterEnd - firstAnnotEnd, -1);
assert.isAbove(qterEnd - lastAnnotEnd, -19);
done();
}
var config = {
organism: 'human',
chrWidth: 10,
chrHeight: 500,
chrMargin: 10,
showChromosomeLabels: true,
annotationsPath: '../dist/data/annotations/all_human_genes.json',
annotationsLayout: 'histogram',
barWidth: 3,
orientation: 'vertical',
dataDir: '/dist/data/bands/native/',
onDrawAnnots: onIdeogramLoadAnnots
};
ideogram = new Ideogram(config);
});
it('should have annotations and brushes aligned with base pairs', function(done) {
// Tests fix for https://github.com/eweitz/ideogram/issues/91
// and related issues.
function getLeft(selector) {
return Math.round(document
.querySelector(selector)
.getBoundingClientRect().x);
}
function getRight(selector) {
return Math.round(document
.querySelector(selector)
.getBoundingClientRect().right);
}
var config = {
organism: 'human',
assembly: 'GRCh37',
chrHeight: 800,
dataDir: '/dist/data/bands/native/',
annotationsLayout: 'histogram',
chromosomes: ['17'],
brush: 'chr17:5000000-10000000',
onBrushMove: function() {},
onLoad: function() {
this.createBrush('17', 1, 2);
this.createBrush('17', 40900000, 44900000);
this.createBrush('17', 81094108, 81094109);
// Closest test for https://github.com/eweitz/ideogram/issues/91
var bandQ2131Left = getLeft('#chr17-9606-q21-31');
var bandQ2131AnnotLeft = getLeft('#chr17-9606 .annot:nth-child(191)');
var bandQ2131BrushLeft = getLeft('#_ideogram > g:nth-child(6) > rect.selection');
assert.equal(bandQ2131AnnotLeft, bandQ2131Left);
assert.equal(bandQ2131AnnotLeft, bandQ2131BrushLeft);
// Check alignment at far left
var firstBpAnnotLeft = getLeft('#chr17-9606 > .annot:nth-child(51)');
var firstBpSliderLeft = getLeft('#_ideogram > g:nth-child(5) > rect.selection');
var firstBpLeft = getLeft('#chr17-9606');
assert.equal(firstBpAnnotLeft, firstBpSliderLeft);
assert.equal(firstBpSliderLeft, firstBpLeft);
// Check alignment at far right
var lastBpAnnotRight = getRight('#chr17-9606 > .annot:nth-child(317)');
var lastBpSliderRight = getRight('#_ideogram > g:nth-child(7) > rect.selection');
var lastBpRight = getRight('#chr17-9606');
assert.isBelow(Math.abs(lastBpAnnotRight - lastBpSliderRight), 3);
assert.isBelow(Math.abs(lastBpSliderRight - lastBpRight), 3);
done();
},
orientation: 'horizontal',
showBandLabels: true, // only work in horizontal mode
annotations: [{
name: 'first_band',
chr: '17',
start: 1,
stop: 2
},
{
name: 'band_q21-31',
chr: '17',
start: 40900000,
stop: 40900001
},
{
name: 'last_band_start',
chr: '17',
start: 75300000,
stop: 75300001
},
{
name: 'last_band_stop',
chr: '17',
start: 81195208,
stop: 81195209
}]
};
var ideogram = new Ideogram(config);
});
it('should have 12 chromosomes per row in small layout example', function(done) {
// Tests use case from ../examples/vanilla/layout-small.html
function callback() {
t1 = d3.select('#chr12-9606-chromosome-set').attr('transform');
t2 = d3.select('#chr13-9606-chromosome-set').attr('transform');
lastChrRow1Y = parseInt(t1.split('translate(')[1].split(',')[0], 10);
firstChrRow2Y = parseInt(t2.split('translate(')[1].split(',')[0], 10);
assert.isTrue(firstChrRow2Y > lastChrRow1Y + config.chrHeight);
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',
dataDir: '/dist/data/bands/native/'
};
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it('should use GRCh37 when specified in "assembly" parameter', function(done) {
// Tests use case from ../examples/vanilla/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 use GCF_000001405.12 when specified in "assembly" parameter', function(done) {
// Tests use case from ../examples/vanilla/human.html with NCBI36 / hg18
function callback() {
var bands = ideogram.chromosomes['9606']['1'].bands;
var chr1Length = bands[bands.length - 1].bp.stop;
assert.equal(chr1Length, 247249719);
done();
}
config.assembly = 'GCF_000001405.12';
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it('should support RefSeq accessions in "assembly" parameter', function(done) {
// Tests use case for non-default assemblies.
// GCF_000306695.2 is commonly called CHM1_1.1
// https://www.ncbi.nlm.nih.gov/assembly/GCF_000306695.2/
function callback() {
var chr1Length = ideogram.chromosomes['9606']['1'].length;
// For reference, see length section of LOCUS field in GenBank record at
// https://www.ncbi.nlm.nih.gov/nuccore/CM001609.2
assert.equal(chr1Length, 250522664);
done();
}
config.assembly = 'GCF_000306695.2';
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it('should support GenBank accessions in "assembly" parameter', function(done) {
// Tests use case for non-default assemblies.
// GCA_000002125.2 is commonly called HuRef
// https://www.ncbi.nlm.nih.gov/assembly/GCA_000002125.2
function callback() {
var chr1Length = ideogram.chromosomes['9606']['1'].length;
// For reference, see length section of LOCUS field in GenBank record at
// https://www.ncbi.nlm.nih.gov/nuccore/CM001609.2
assert.equal(chr1Length, 219475005);
done();
}
config.assembly = 'GCA_000002125.2';
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it('should recover chromosomes when given scaffolds', function(done) {
// Tests use case from ../examples/vanilla/human.html
function callback() {
var numChromosomes = document.querySelectorAll('.chromosome').length;
assert.equal(numChromosomes, 20);
done();
}
var config = {
organism: 'Sus scrofa', // pig
onLoad: callback
};
var ideogram = new Ideogram(config);
});
it('should support mitochondrial and chloroplast chromosomes', function(done) {
// Tests use case from ../examples/vanilla/eukaryotes.html
function callback() {
var chromosomes = Array.from(document.querySelectorAll('.chromosome'));
var nonNuclearChrs = chromosomes.slice(-2);
assert.equal(chromosomes.length, 21);
assert.equal(nonNuclearChrs[0].id, 'chrCP-29760'); // chloroplast (CP)
assert.equal(nonNuclearChrs[1].id, 'chrMT-29760'); // mitochrondrion (MT)
done();
}
var config = {
organism: 'vitis-vinifera', // grape
showNonNuclearChromosomes: true,
onLoad: callback
};
var ideogram = new Ideogram(config);
});
it('should support apicoplast chromosomes of malaria parasite', function(done) {
// Tests use case from ../examples/vanilla/eukaryotes.html
function callback() {
var chromosomes = Array.from(document.querySelectorAll('.chromosome'));
var nonNuclearChrs = chromosomes.slice(-1);
assert.equal(chromosomes.length, 15);
assert.equal(nonNuclearChrs[0].id, 'chrAP-5833'); // apicoplast (CP)
done();
}
var config = {
organism: 'plasmodium-falciparum', // P. falciparum, malaria parasite
showNonNuclearChromosomes: true,
onLoad: callback
};
var ideogram = new Ideogram(config);
});
it('should handle arrayed objects in "annotations" parameter', function(done) {
// Tests use case from ../examples/vanilla/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 create a brush when specified', function(done) {
// Tests use case from ../examples/vanilla/brush.html
function callback() {
assert.equal(ideogram.selectedRegion.from, 5000000);
assert.equal(ideogram.selectedRegion.to, 10000000);
assert.equal(ideogram.selectedRegion.extent, 5000000);
assert.equal(d3.selectAll('.selection').nodes().length, 1);
done();
}
var config = {
organism: 'human',
chromosome: '19',
brush: 'chr19:5000000-10000000',
chrHeight: 900,
orientation: 'horizontal',
onBrushMove: callback, // placeholder
onLoad: callback,
dataDir: '/dist/data/bands/native/'
};
var ideogram = new Ideogram(config);
});
// TODO: Re-enable when there is a decent package that enables
// PhantomJS-like screenshots from automated tests
// cf.:
// if (window.callPhantom) {
// callPhantom({'screenshot': filename})
//
// it('should align chr. label with thick horizontal chromosome', function(done) {
// // Tests use case from ../examples/vanilla/annotations_basic.html
//
// function callback() {
// var band, bandMiddle,
// chrLabel, chrLabelMiddle;
//
// band = d3.selectAll(".chromosome .band").nodes()[0].getBoundingClientRect();
// chrLabel = d3.selectAll(".chrSetLabel").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/vanilla/human.html
function callback() {
var band, bandMiddle,
chrLabel, chrLabelMiddle;
band = d3.selectAll('.chromosome .band').nodes()[0].getBoundingClientRect();
chrLabel = d3.selectAll('.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: '../dist/data/annotations/1000_virtual_snvs.json',
annotationTracks: annotationTracks,
annotationHeight: 2.5,
dataDir: '/dist/data/bands/native/'
};
config.onDrawAnnots = callback;
var ideogram = new Ideogram(config);
});
it('should show three human genomes in one page', function(done) {
// Tests use case from ../examples/vanilla/multiple-trio.html
var config, containerIDs, id, i, container,
ideogramsLoaded = 0;
function callback() {
var numChromosomes;
ideogramsLoaded += 1;
if (ideogramsLoaded === 3) {
numChromosomes = document.querySelectorAll('.chromosome').length;
assert.equal(numChromosomes, 24 * 3);
done();
}
}
config = {
organism: 'human',
chrHeight: 125,
resolution: 400,
orientation: 'vertical',
dataDir: '/dist/data/bands/native/',
onLoad: callback
};
containerIDs = ['mother', 'father', 'proband'];
for (i = 0; i < containerIDs.length; i++) {
id = containerIDs[i];
container = '<div id="' + id + '"></div>';
document.querySelector('body').innerHTML += container;
config.container = '#' + id;
new Ideogram(config);
}
});
it('should show three unbanded, annotated primate genomes in one page', function(done) {
// Tests use case from ../examples/vanilla/multiple-primates.html
var config, containerIDs, id, i, container,
ideogramsLoaded = 0,
annotSetsDrawn = 0;
function callback() {
var numChromosomes;
ideogramsLoaded += 1;
if (ideogramsLoaded === 3) {
numChromosomes = document.querySelectorAll('.chromosome').length;
assert.equal(numChromosomes, 24 + 25 + 21);
}
}
function onDrawAnnotsCallback() {
var numAnnots;
annotSetsDrawn += 1;
if (annotSetsDrawn === 3) {
numAnnots = document.querySelectorAll('.annot').length;
assert.equal(numAnnots, 6);
console.log('done?')
done();
}
}
var orgConfigs = [
{
organism: 'homo-sapiens',
annotations: [
{name: 'APOB', chr: '2', start: 21001429, stop: 21044073, color: '#F00'},
{name: 'CTLA4', chr: '2', start: 203867788, stop: 203873960, color: '#77F', shape: 'circle'}
]
},
{
organism: 'pan-troglodytes',
annotations: [
{name: 'APOB', chr: '2A', start: 21371172, stop: 21413720, color: '#F00'},
{name: 'CTLA4', chr: '2B', start: 94542849, stop: 94550230, color: '#77F', shape: 'circle'}
]
},
{
organism: 'macaca-fascicularis',
annotations: [
{name: 'APOB', chr: '13', start: 89924186, stop: 89966894, color: '#F00'},
{name: 'CTLA4', chr: '12', start: 93412707, stop: 93419132, color: '#77F', shape: 'circle'}
]
}
];
config = {
chrHeight: 250,
chrMargin: 2,
orientation: 'horizontal',
showFullyBanded: false,
dataDir: '/dist/data/bands/native/',
onLoad: callback,
onDrawAnnots: onDrawAnnotsCallback
};
containerIDs = ['homo-sapiens', 'pan-troglodytes', 'macaca-fascicularis'];
for (i = 0; i < containerIDs.length; i++) {
id = containerIDs[i];
container = '<div id="' + id + '"></div>';
document.querySelector('body').innerHTML += container;
config.container = '#' + id;
config.organism = id;
config.annotations = orgConfigs[i].annotations;
new Ideogram(config);
}
});
// This test is flaky in Travis CI.
// Disabled until a way to detect Travis environment is found.
// it('should show border of band-labeled chromosome when multiple ideograms exist', function(done) {
// // Tests fix for https://github.com/eweitz/ideogram/issues/96
//
// var config1, ideogram1, config2, ideogram2, width;
//
// function callback() {
// width =
// document
// .querySelectorAll('#chr7-9606-example2 .chromosome-border path')[1]
// .getBBox().width;
//
// width = Math.round(width);
//
// console.log('495 - width')
// console.log(495 - width)
//
// assert.equal(495 - width, 0);
//
// console.log('ok')
//
// done();
// }
//
// document.querySelector('body').innerHTML +=
// '<div id="example1"></div>' +
// '<div id="example2"></div>';
//
// config1 = {
// container: '#example1',
// organism: 'human',
// orientation: 'horizontal',
// dataDir: '/dist/data/bands/native/',
// annotations: [
// {
// chr: '2',
// start: 34294,
// stop: 125482
// },
// {
// chr: '17',
// start: 43125400,
// stop: 43125482
// }
// ]
// };
//
// ideogram1 = new Ideogram(config1);
//
// config2 = {
// container: '#example2',
// organism: 'human',
// chromosome: '7',
// orientation: 'horizontal',
// annotations: [
// {
// chr: '7',
// start: 199999,
// stop: 3000000
// },
// {
// chr: '7',
// start: 6000000,
// stop: 9000000
// }
// ],
// annotationsLayout: 'overlay',
// dataDir: '/dist/data/bands/native/',
// onDrawAnnots: callback
// };
//
// ideogram2 = new Ideogram(config2);
//
// });
it('should show XX chromosomes for a diploid human female', function(done) {
// Tests use case from ../examples/vanilla/ploidy-basic.html
function callback() {
var selector = '#chrX-9606-chromosome-set .chrSetLabel tspan';
var chrSetLabel = getSvgText(selector);
assert.equal(chrSetLabel, 'XX');
done();
}
var config = {
organism: 'human',
sex: 'female',
chrHeight: 300,
chrWidth: 8,
ploidy: 2,
dataDir: '/dist/data/bands/native/',
onLoad: callback
};
var ideogram = new Ideogram(config);
});
it('should show XY chromosomes for a diploid human male', function(done) {
// Tests use case from ../examples/vanilla/ploidy-basic.html
function callback() {
var selector = '#chrX-9606-chromosome-set .chrSetLabel tspan';
var chrSetLabel = getSvgText(selector);
assert.equal(chrSetLabel, 'XY');
done();
}
var config = {
organism: 'human',
sex: "male",
chrHeight: 300,
chrWidth: 8,
ploidy: 2,
dataDir: '/dist/data/bands/native/',
onLoad: callback
};
var ideogram = new Ideogram(config);
});
it('should omit Y chromosome in haploid human female', function(done) {
function callback() {
var hasChrY = d3.selectAll('#chrY-9606').nodes().length >= 1;
assert.isFalse(hasChrY);
done();
}
var config = {
organism: 'human',
sex: 'female',
dataDir: '/dist/data/bands/native/'
};
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it('should support using NCBI Taxonomy ID in "organism" option', function(done) {
function callback() {
var numChromosomes = Object.keys(ideogram.chromosomes[9606]).length;
assert.equal(numChromosomes, 24);
done();
}
var config = {
organism: 9606,
dataDir: '/dist/data/bands/native/'
};
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it('should handle toggling single- and multi-chromosome view, in vertical orientation', function(done) {
function callback() {
d3.select('#chr1-9606').dispatch('click');
var shownChrs = d3.selectAll('.chromosome').nodes().filter(function(d) {
return d.style.display !== 'none';
});
var shownChrID = shownChrs[0].id;
assert.equal(shownChrs.length, 1);
assert.equal(shownChrID, 'chr1-9606');
d3.select('#chr1-9606').dispatch('click');
setTimeout(function() {
var shownChrs = d3.selectAll('.chromosome').nodes().filter(function(d) {
return d.style.display !== 'none';
});
assert.equal(shownChrs.length, 24);
done();
}, 500);
}
config.onLoad = callback;
var ideogram = new Ideogram(config);
});
it('should handle toggling single- and multi-chromosome view, in horizontal orientation', function(done) {
function callback() {
d3.select('#chr1-9606').dispatch('click');
var shownChrs = d3.selectAll('.chromosome').nodes().filter(function(d) {
return d.style.display !== 'none';
});
var shownChrID = shownChrs[0].id;
assert.equal(shownChrs.length, 1);
assert.equal(shownChrID, 'chr1-9606');
d3.select('#chr1-9606').dispatch('click');
setTimeout(function() {
var shownChrs = d3.selectAll('.chromosome').nodes().filter(function(d) {
return d.style.display !== 'none';
});
assert.equal(shownChrs.length, 24);
done();
}, 500);
}
config.onLoad = callback;
config.orientation = 'horizontal';
var ideogram = new Ideogram(config);
});
it('should handle toggling single- and multi-chromosome view, in labeled vertical orientation', function(done) {
// Tests that band labels remain visible after rotating vertical chromosomes
function callback