cyto-chromosome-vis
Version:
Interactive visual representation/ web visualization tool for chromosome ideograms
790 lines (638 loc) • 20.9 kB
JavaScript
(function() {
var cyto_chr = {}, d3 = window.d3;
if (typeof module !== 'undefined' && module.exports) {
module.exports = cyto_chr;
d3 = require('d3');
} else if(typeof angular !== 'undefined') {} else {
window.cyto_chr = cyto_chr;
}
(function(cyto_chr, d3) {
'use strict'
cyto_chr.margin = {
top: 38,
left: 14,
right: 5
};
var CHR1_BP_END = 248956422;
var CHR1_BP_MID = 121700000;
var Chromosome = function() {
this._segment = '1';
this._selectionMode = 'single';
this._domTarget = d3.select(document.documentElement);
this._resolution = "550";
this._width = null;
this._height = 17;
this._useRelative = true;
this._showAxis = false;
this.dispatch = d3.dispatch('bandclick', 'selectorchange', 'selectorend', 'selectordelete');
this.rendered = false;
this.selectors = [];
this.model = [];
this.maxBasePair = 0;
this.xscale = d3.scale.linear();
};
Chromosome.prototype.getMaxBasepair = function() {
return this.maxBasePair;
};
Chromosome.prototype.segment = function (a) {
if (typeof a === 'number') a = a.toString();
return cyto_chr.InitGetterSetter.call(this, '_segment', a);
};
Chromosome.prototype.selectionMode = function (a) {
return cyto_chr.InitGetterSetter.call(this, '_selectionMode', a);
};
Chromosome.prototype.target = function (a) {
if(typeof a === 'string') a = d3.select(a);
if(a.empty()) {
throw "Error: Invalid dom target";
}
return cyto_chr.InitGetterSetter.call(this, '_domTarget', a);
};
Chromosome.prototype.resolution = function (a) {
if (typeof a === 'number') a = a.toString();
if (a === "400" || a === "550" || a ==="850" || a === "1200" || a === undefined) {
return cyto_chr.InitGetterSetter.call(this, '_resolution', a);
} else {
throw "Error: Invalid resolution. Please enter 400, 550, 850, or 1200 only.";
}
};
Chromosome.prototype.width = function (a) {
return cyto_chr.InitGetterSetter.call(this, '_width', a);
};
Chromosome.prototype.height = function (a) {
return cyto_chr.InitGetterSetter.call(this, '_height', a);
};
Chromosome.prototype.useRelative = function (a) {
return cyto_chr.InitGetterSetter.call(this, '_useRelative', a);
};
Chromosome.prototype.showAxis = function (a) {
return cyto_chr.InitGetterSetter.call(this, '_showAxis', a);
};
Chromosome.prototype.on = function(e, listener) {
if (!this.dispatch.hasOwnProperty(e)) throw "Error: No event for " + e;
this.dispatch.on(e, listener);
};
Chromosome.prototype.config = function(type, arg) {
return this[type](arg);
};
Chromosome.prototype.renderAxis = function () {
var bpAxis = d3.svg.axis()
.scale(this.xscale)
.tickFormat(d3.format('s'))
.orient("bottom");
if (this._useRelative && (this._segment === "Y" || this._segment === "22" || this._segment === "21" || this._segment === "20" || this._segment === "19")) {
bpAxis.ticks(6);
}
var axisg = this.svgTarget.append('g')
.classed('bp-axis', true)
.attr('transform', 'translate('+ cyto_chr.margin.left + ',' + (this._height + cyto_chr.margin.top + 6) + ")");
axisg.call(bpAxis);
axisg.selectAll('text')
.style('font', '10px sans-serif');
axisg.selectAll('path, line')
.style({
"fill": "none",
"stroke": "#666666",
"shape-rendering": "crispEdges"
});
};
Chromosome.prototype.remove = function() {
this.svgTarget.remove();
};
Chromosome.prototype.moveSelectorTo = function(start, stop) {
if(arguments.length !== 2) {
throw "Error moveSelectorTo: Invalid number of arguments. Both start and stop coordinates are required";
}
if (this.selectors.length === 0) {
this.newSelector(0, "10000000");
} else {
this.selectors[0].move(start, stop);
}
};
Chromosome.prototype.newSelector = function(bp_start, bp_stop) {
var self = this;
function selectorRemoveCB(sel) {
self.dispatch.selectordelete(sel);
var index = self.selectors.indexOf(sel);
self.selectors.splice(index, 1);
}
var ve = cyto_chr.selector(selectorRemoveCB)
.x(cyto_chr.margin.left)
.y(cyto_chr.margin.top - (this._height / 4))
.height(this._height + (this._height / 2))
.xscale(this.xscale)
.extent([bp_start, bp_stop])
.target(this.svgTarget)
.render();
ve.dispatch.on('change', function(d) {
self.dispatch.selectorchange(d);
});
ve.dispatch.on('changeend', function(d) {
self.dispatch.selectorend(d);
});
this.selectors.push(ve);
};
Chromosome.prototype.getSelections = function() {
var ret = [];
for(var i = 0; i < this.selectors.length; i++) {
var sel = this.selectors[i].extent();
ret.push({
start: sel[0],
stop: sel[1]
})
}
return ret;
};
Chromosome.prototype.getSelectedBands = function(sensitivity) {
var results = [];
if (this.selectors.length > 0) {
var se = this.selectors[0].extent();
var selStart = +se[0];
var selStop = +se[1];
if (typeof sensitivity !== 'undefined') {
selStart -= sensitivity;
selStop += sensitivity;
}
results = this.model.slice().filter(function(e) {
var bStart = +e.bp_start;
var bStop = +e.bp_stop;
if ((selStart >= bStart && selStart < bStop) ||
(selStop > bStart && selStop <= bStop) ||
(selStart <= bStart && selStop >= bStop)) {
return true;
} else {
return false;
}
});
}
return results;
};
Chromosome.prototype.getSVGTarget = function() {
return this.svgTarget;
};
Chromosome.prototype.render = function (zoomRange) {
var self = this;
if(self.rendered) {
self.selectors = [];
self.remove();
}
if(self._width === null) {
var parentWidth = d3.select(self._domTarget[0][0].parentNode).node().getBoundingClientRect().width;
self.width(parentWidth)
}
cyto_chr.modelLoader.load(this._segment, this._resolution, function(data) {
self.model = data;
self.maxBasePair = d3.max(data, function(d) {
return +d.bp_stop;
});
self.segMid = 0;
for(var j =0; j < data.length;j++) {
if(data[j].stain ==="acen") {
self.segMid = data[j].bp_stop;
break;
}
}
var rangeTo = self._useRelative ? (self.maxBasePair / CHR1_BP_END) * self._width : self._width;
rangeTo -= (cyto_chr.margin.left + cyto_chr.margin.right);
if (zoomRange) {
self.xscale.domain([zoomRange[0], zoomRange[1]]);
} else {
self.xscale.domain([1, self.maxBasePair]);
}
self.xscale.range([0, rangeTo]);
var h = self._height + 62;
var w = self._width;
self.svgTarget = self._domTarget
.style('height', h + 'px')
.style('width', (w+5) + 'px')
.append('svg')
.attr('width', w)
.attr('height', h);
var bands = self.svgTarget.selectAll('g')
.data(data).enter();
self.svgTarget.append('text')
.text(self._segment)
.attr('x', 5)
.attr('y', cyto_chr.margin.top + (self._height/ 2) + 2)
.attr('text-anchor','middle')
.style('font', '10px sans-serif');
function bpCoord(bp) {
var xshift = 0;
if(self.alignCentromere && self._segment !== "1") {
xshift = self.xscale(CHR1_BP_MID) - self.xscale(self.segMid);
}
return self.xscale(bp) + cyto_chr.margin.left + xshift;
}
bands.append('g')
.each(function(d, i) {
var elem = d3.select(this);
function applyBorder() {
this
.attr('stroke', '#000000')
.attr('stroke-width', 0.2);
}
function drawRoundedRect(d, r, tl, tr, bl, br) {
return this.append('path')
.attr("d", cyto_chr.roundedRect(bpCoord(d.bp_start), cyto_chr.margin.top, bpCoord(d.bp_stop) - bpCoord(d.bp_start), self._height, r, tl, tr, bl, br))
.style('fill', cyto_chr.getStainColour(d.stain, d.density));
}
var labelSkipFactor = self._resolution === '1200' ? 8 : 2;
if (self._useRelative && self._resolution == "1200" && self._segment == 'Y') {
labelSkipFactor = 12;
}
if(i % labelSkipFactor === 0) {
var bmid = (bpCoord(d.bp_stop) + bpCoord(d.bp_start)) / 2;
elem.append('line')
.attr('x1', bmid)
.attr('y1', cyto_chr.margin.top)
.attr('x2', bmid)
.attr('y2', cyto_chr.margin.top - 4)
.style('stroke', 'grey')
.style('stroke-width',1);
elem.append('text')
.attr('transform', 'translate(' + bmid + ',' + (cyto_chr.margin.top - 6) + ')rotate(-50)')
.style('font', '10px sans-serif')
.text(d.arm + d.band);
}
var rect;
var w = bpCoord(d.bp_stop) - bpCoord(d.bp_start);
var acenThreshold = (self._resolution == "1200") ? 7 : 6;
if (i === 0 && w > 10) {
rect = drawRoundedRect.call(elem, d, 4, true, false, true, false);
applyBorder.call(rect);
} else if (d.stain === "acen" && (w > acenThreshold)) {
if (d.arm === "p") {
rect = drawRoundedRect.call(elem, d, 5, false, true, false, true);
} else if(d.arm === "q") {
rect = drawRoundedRect.call(elem, d, 5, true, false, true, false);
}
} else if (i === data.length - 1) {
rect = drawRoundedRect.call(elem, d, 5, false, true, false, true);
applyBorder.call(rect);
} else {
var ys = d.stain === "stalk" ? cyto_chr.margin.top + (self._height / 4) : cyto_chr.margin.top;
var hs = d.stain === "stalk" ? self._height / 2 : self._height;
rect = elem.append('rect')
.attr('x', bpCoord(d.bp_start))
.attr('y', ys)
.attr('height', hs)
.attr('width', self.xscale(d.bp_stop) - self.xscale(d.bp_start))
.style('fill', cyto_chr.getStainColour(d.stain, d.density));
applyBorder.call(rect);
}
rect.append('title')
.text(d.arm + d.band)
rect.on('mouseover', function(d) {
var e = d3.select(this)
.style('opacity', "0.5")
.style('cursor', 'pointer');
if (d.stain === "gneg") {
e.style('fill', cyto_chr.getStainColour("gpos", "25"));
}
});
rect.on('mouseout', function(d) {
var e = d3.select(this)
.style('opacity', "1")
.style('cursor', 'default');
if (d.stain === "gneg") {
e.style('fill', cyto_chr.getStainColour("gneg"));
}
});
rect.on('click', function(d) {
if (self.selectors.length === 0 || (self._selectionMode === 'multi' && d3.event.altKey)) {
self.newSelector(d.bp_start, d.bp_stop);
}
if (self._selectionMode === 'single' && self.selectors.length > 0) {
self.moveSelectorTo(d.bp_start, d.bp_stop);
}
self.dispatch.bandclick(d);
});
});
if (self._showAxis) {
self.renderAxis();
}
self.rendered = true;
});
return self;
};
cyto_chr.chromosome = function() {
return new Chromosome();
};1
})(cyto_chr || {}, d3);
(function (cyto_chr, d3) {
var defaultDataURLs = {
"400" : "ideogram_9606_GCF_000001305.14_400_V1",
"550" : "ideogram_9606_GCF_000001305.14_550_V1",
"850" : "ideogram_9606_GCF_000001305.14_850_V1",
"1200" : "ideogram_9606_GCF_000001305.13_1200_v1"
};
var baseDir = 'data/';
function CacheInstance() {
this.status = "notloaded";
this.cache = [];
}
var dataCache = {
"400" : new CacheInstance,
"550" : new CacheInstance,
"850" : new CacheInstance,
"1200" : new CacheInstance
};
var callQueue = [];
function loadData(file, res, cb) {
var c = dataCache[res];
if (c.cache.length === 0) {
if (c.status === "loading") {
callQueue.push({
res: res,
cb: cb
});
return;
} else if (c.status === "notloaded") {
c.status = "loading";
d3.tsv(file, function(d) {
c.cache = d;
c.status = "loaded";
cb(d);
while(callQueue.length > 0) {
var cbq = callQueue.shift();
cbq.cb(d);
}
});
}
} else {
cb(c.cache);
}
}
function getChromosomeData(chr, resolution, cb) {
chr = chr || '1';
resolution = resolution || "550";
var fileName = defaultDataURLs[resolution];
loadData(baseDir + fileName, resolution, function (d) {
if(d) {
var filteredResults = filterByChromosome(d, chr);
cb(filteredResults);
}
});
}
function filterByChromosome(data, chr) {
var newAry = [];
for(var i = 0; i < data.length; i++) {
if (data[i]['#chromosome'] === chr) {
newAry.push(data[i]);
}
}
return newAry;
}
cyto_chr.modelLoader = {
load: getChromosomeData,
setDataDir: function(d) {baseDir = d;},
getDataDir: function() {return baseDir;}
};
})(cyto_chr || {}, d3);
(function(cyto_chr, d3) {
var Selector = function(closecb) {
this._brush = d3.svg.brush();
this.dispatch = d3.dispatch('change', 'changeend', 'selectordelete');
this._x = 0;
this._y = 0;
this._extent = [0,0];
this._height = 0;
this._closecb = closecb;
};
Selector.prototype.test = function(e) {
var self = this;
return cyto_chr.InitGetterSetter.call(this, "_test", e, function(){
self._another = "_that";
});
};
Selector.prototype.extent = function (a) {
var self = this;
if(typeof a === "undefined") {
return self._brush.extent();
}
return cyto_chr.InitGetterSetter.call(this, "_extent", a, function(){
self._brush.extent(a);
});
};
Selector.prototype.xscale = function(a) {
var self = this;
return cyto_chr.InitGetterSetter.call(this, "_xscale", a, function(){
self._brush.x(a);
});
};
Selector.prototype.target = function(a) {
return cyto_chr.InitGetterSetter.call(this, '_target', a);
};
Selector.prototype.height = function(a) {
return cyto_chr.InitGetterSetter.call(this, '_height', a);
};
Selector.prototype.x = function(a) {
return cyto_chr.InitGetterSetter.call(this, '_x', a);
};
Selector.prototype.y = function(a) {
return cyto_chr.InitGetterSetter.call(this, '_y', a);
};
Selector.prototype.render = function() {
var self = this;
this.selector = this._target.append('g')
.attr('transform', 'translate(' + this._x + ',' + this._y + ')')
.call(this._brush);
this.selector.selectAll('rect')
.attr('height', this._height);
this.selector.select('.background').remove();
var e = this.selector.select('.extent')
.style('fill', 'steelblue')
.style('opacity', '0.5');
var cbg_xpos = this._xscale(this._extent[1]) + cyto_chr.margin.left;
var cbg_ypos = cyto_chr.margin.top - 3;
var cbg = this._target.append('g');
cbg.append('title').text('remove');
this.deleteButton = cbg.append('circle')
.attr('cx', cbg_xpos)
.attr('cy', cbg_ypos)
.attr('r', 5)
.attr('fill', 'red')
.on('mouseover', function() {
d3.select(this)
.style('cursor', 'pointer');
})
.on('mouseout', function(){
d3.select(this)
.style('cursor', 'default');
})
.on('click', function() {
self.remove();
});
this._brush.on('brush', function() {
self.updateXButton();
var ext = self._brush.extent();
self.dispatch.change(ext);
});
this._brush.on('brushend', function(d) {
var ext = self._brush.extent();
self.dispatch.changeend(ext);
});
return this;
};
Selector.prototype.remove = function() {
this.selector.remove();
this.deleteButton.remove();
this._closecb(this);
return this;
};
Selector.prototype.updateXButton = function() {
var e = this._brush.extent();
var new_xpos = this._xscale(e[1]) + cyto_chr.margin.left;
this.deleteButton.attr('cx', new_xpos);
};
Selector.prototype.move = function(start, stop) {
this._brush.extent([start, stop]);
this.selector.call(this._brush);
this.updateXButton();
};
cyto_chr.selector = function(cb){
return new Selector(cb);
};
})(cyto_chr || {}, d3);
(function(cyto_chr, d3) {
cyto_chr.initPattern = function () {
var pg = this.append('pattern')
.attr('id', 'acen-fill')
.attr('patternUnits', 'userSpaceOnUse')
.attr('x', '0')
.attr('y', '0')
.attr('width', '10')
.attr('height', '10')
.append('g')
.style({
"fill": "none",
"stroke": "#708090",
"stroke-width": "2"
});
pg.append('path')
.attr('d', "M0,0 l10,10");
pg.append('path')
.attr('d','M10,0 l-10,10');
};
cyto_chr.roundedRect = function (x, y, w, h, r, tl, tr, bl, br) {
var retval;
retval = "M" + (x + r) + "," + y;
retval += "h" + (w - 2 * r);
if (tr) {
retval += "a" + r + "," + r + " 0 0 1 " + r + "," + r;
} else {
retval += "h" + r;
retval += "v" + r;
}
retval += "v" + (h - 2 * r);
if (br) {
retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + r;
} else {
retval += "v" + r;
retval += "h" + -r;
}
retval += "h" + (2 * r - w);
if (bl) {
retval += "a" + r + "," + r + " 0 0 1 " + -r + "," + -r;
} else {
retval += "h" + -r;
retval += "v" + -r;
}
retval += "v" + (2 * r - h);
if (tl) {
retval += "a" + r + "," + r + " 0 0 1 " + r + "," + -r;
} else {
retval += "v" + -r;
retval += "h" + r;
}
retval += "z";
return retval;
};
cyto_chr.getStainColour = function (bandtype, density) {
if(bandtype === "gpos") {
if(density === "" || density === null) { return "#000000"; }
switch(density) {
case "100":
return "#000000";
case "75":
return "#666666";
case "50":
return "#999999";
case "25":
return "#d9d9d9";
}
}
if (bandtype === "gneg") {
return "#ffffff";
}
if (bandtype === "acen") {
//return "url(#acen-fill)";
return "#708090";
}
if (bandtype === "gvar") {
return "#e0e0e0";
}
if(bandtype === "stalk") {
return "#708090";
}
return "green";
};
cyto_chr.setOption = function (userOption, def) {
if(typeof userOption !== "undefined") {
return userOption;
} else {
return def;
}
};
cyto_chr.InitGetterSetter = function(prop, arg, cb) {
if(typeof arg !== 'undefined') {
this[prop] = arg;
if(typeof cb === 'function') {
cb();
}
return this;
} else {
return this[prop];
}
};
})(cyto_chr || {}, d3);
(function(cyto_chr, d3){
if (typeof angular === 'undefined') {
return;
}
cyto_chr.modelLoader.setDataDir('./node_modules/cyto-chromosome-vis/data/');
angular.module('cyto-chromosome-vis',[])
.directive('cytochromosome',[function() {
function link(scope, element, attr) {
attr.resolution = cyto_chr.setOption(attr.resolution, "550");
attr.width = cyto_chr.setOption(attr.width, 1000);
attr.segment = cyto_chr.setOption(attr.segment, "1");
attr.useRelative = cyto_chr.setOption(attr.useRelative, true);
attr.showAxis = cyto_chr.setOption(attr.showAxis, false);
cyto_chr.chromosome()
.target(d3.select(element[0]))
.width(attr.width)
.segment(attr.segment)
.resolution(attr.resolution)
.useRelative(attr.useRelative == "true")
.showAxis(attr.showAxis == "true")
.render();
}
return {
link: link,
restrict: 'E'
};
}])
.provider('cytochromosome', function(){
this.build = function() {
return cyto_chr.chromosome();
};
this.setDataDir = function(d) {
cyto_chr.modelLoader.setDataDir(d);
};
this.$get = function() {
return this;
};
});
})(cyto_chr || {}, d3);
}());