ScriblHTML5 Canvas Genomics Graphics Library | |
| Scribl.js |
Scribl Class
sets defaults, defines how to add features
to chart/view and some methods to help
coordinate drawing
Chase Miller 2011
|
var SCRIBL = {};
SCRIBL.chars = {};
SCRIBL.chars.nt_color = 'white';
SCRIBL.chars.nt_A_bg = 'red';
SCRIBL.chars.nt_G_bg = 'blue';
SCRIBL.chars.nt_C_bg = 'green';
SCRIBL.chars.nt_T_bg = 'black';
SCRIBL.chars.nt_N_bg = 'purple';
SCRIBL.chars.nt_dash_bg = 'rgb(120,120,120)';
SCRIBL.chars.heights = [];
SCRIBL.chars.canvasHolder = document.createElement('canvas');
var Scribl = Class.extend({
|
init
Constructor, call this with new Scribl()
param: Object canvasHTML object param: Int width of chart in pixels return: Object Scribl object api: public
|
init: function(canvas, width) {
this.scrolled = false;
var ctx;
if (canvas)
ctx = canvas.getContext('2d');
var chart = this;
this.width = width;
this.uid = _uniqueId('chart');
this.laneSizes = 50;
this.laneBuffer = 5;
this.trackBuffer = 25;
this.offset = undefined;
this.canvas = canvas;
this.ctx = ctx;
this.scale = {};
this.scale.pretty = true;
this.scale.max = undefined;
this.scale.min = undefined;
this.scale.auto = true;
this.scale.userControlled = false;
this.scale.positions = [0];
this.scale.off = false;
this.scale.size = 15;
this.scale.font = {};
this.scale.font.size = 15;
this.scale.font.color = 'black';
this.scale.font.buffer = 10;
this.glyph = {};
this.glyph.roundness = 6;
this.glyph.borderWidth = 1;
this.glyph.color = ['#99CCFF', 'rgb(63, 128, 205)'];
this.glyph.text = {};
this.glyph.text.color = 'black';
this.glyph.text.size = '13';
this.glyph.text.font = 'arial';
this.glyph.text.align = 'center';
this.gene = {};
this.gene.text = {};
this.protein = {};
this.protein.text = {};
this.events = {};
this.events.hasClick = false;
this.events.hasMouseover = false;
this.events.clicks = new Array;
this.events.mouseovers = new Array;
this.events.added = false;
this.mouseHandler = function(e) {
chart.handleMouseEvent(e, 'mouseover')
};
this.clickHandler = function(e) { chart.handleMouseEvent(e, 'click') };
this.tick = {};
this.tick.auto = true;
this.tick.major = {};
this.tick.major.size = 10;
this.tick.major.color = 'black';
this.tick.minor = {};
this.tick.minor.size = 1;
this.tick.minor.color = 'rgb(55,55,55)';
this.tick.halfColor = 'rgb(10,10,10)';
this.tooltips = {};
this.tooltips.text = {}
this.tooltips.text.font = 'arial';
this.tooltips.text.size = 12;
this.tooltips.borderWidth = 1;
this.tooltips.roundness = 5;
this.tooltips.fade = false;
this.tooltips.style = 'light';
this.lastToolTips = [];
this.scrollable = false;
this.scrollValues = [0, undefined];
this.chars = {};
this.chars.drawOnBuild = [];
this.drawStyle = 'expand';
this.glyphHooks = [];
this.trackHooks = [];
this.myMouseEventHandler = new MouseEventHandler(this);
this.tracks = [];
var scaleSize = this.scale.size;
var scaleFontSize = this.scale.font.size
},
|
getScaleHeight
Get the height of the scale/ruler
|
getScaleHeight: function() {
return (this.scale.font.size + this.scale.size);
},
|
getHeight
Get the height of the entire Scribl chart/view
|
getHeight: function() {
var wholeHeight = 0;
if (!this.scale.off) wholeHeight += this.getScaleHeight();
var numTracks = this.tracks.length
for (var i=0; i < numTracks; i++) {
wholeHeight += this.trackBuffer;
wholeHeight += this.tracks[i].getHeight();
}
return wholeHeight;
},
|
getFeatures
Returns an array of features (e.g. gene)
|
getFeatures: function() {
var features = [];
for (var i=0; i < this.tracks.length; i++){
for (var k=0; k < this.tracks[i].lanes.length; k++) {
features = features.concat(this.tracks[i].lanes[k].features);
}
}
return features;
},
|
setCanvas
Changes the canvas that Scribl draws to
|
setCanvas: function(canvas){
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
},
|
addScale
Inserts a scale at the end of the last track currently added to the chart
|
addScale: function() {
if (this.scale.userControlled)
this.scale.positions.push( this.tracks.length );
else {
this.scale.positions = [ this.tracks.length ];
this.scale.userControlled = true;
}
},
|
addTrack
Creates a new track and adds it to the Scribl chart/view
|
addTrack: function() {
var track = new Track(this);
if (this.tracks.length == 1 && this.tracks[0] == undefined)
this.tracks = [];
this.tracks.push(track);
return track;
},
|
removeTrack
removes a track
|
removeTrack: function(track) {
var chart = this;
for (var i=0; i < chart.tracks.length; i++){
if (track.uid == chart.tracks[i].uid)
chart.tracks.splice(i,1);
}
delete track;
},
|
loadGenbank
parses a genbank file and adds the features to the Scribl chart/view
|
loadGenbank: function(file) {
genbank(file, this);
},
|
loadBed
parses a bed file and adds the features to the Scribl chart/view
|
loadBed: function(file) {
bed(file, this);
},
|
loadBam
parses a bam file and adds the features to the Scribl chart/view
|
loadBam: function(bamFile, baiFile, chr, start, end, callback) {
var scribl = this;
var track = scribl.addTrack();
track.status = 'waiting';
makeBam(new BlobFetchable(bamFile),
new BlobFetchable(baiFile),
function(bam, reader) {
scribl.file = bam;
bam.fetch(chr, start, end, function(r, e) {
if (r) {
for (var i = 0; i < r.length; i += 1) {
track.addFeature( new BlockArrow('bam', r[i].pos, r[i].lengthOnRef, '+', {'seq':r[i].seq}))
}
track.status = "received";
if (track.drawOnResponse)
scribl.redraw();
}
if (e) {
alert('error: ' + e);
}
});
});
return track;
},
|
loadFeatures
adds the features to the Scribl chart/view
param: Array features - array of features, which can be any of the derived Glyph classes (e.g. Rect, Arrow, etc..) api: public
|
loadFeatures: function(features) {
for ( var i=0; i < features.length; i++ )
this.addFeature( features[i] );
},
|
addGene
syntactic sugar function to add a feature with the gene type
param: Int position - start position of the feature param: Int length - length of the feature param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of options that can be applied to feature return: Object feature - a feature with the 'feature' type api: public
|
addGene: function (position, length, strand, opts) {
return (this.addFeature(
new BlockArrow('gene', position, length, strand, opts)
));
},
|
addProtein
syntactic sugar function to add a feature with the protein type
param: Int position - start position of the protein param: Int length - length of the protein param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of options that can be applied to protein return: Object protein - a feature with the 'protein' type api: public
|
addProtein: function(position, length, strand, opts) {
return (this.addFeature(
new BlockArrow('protein', position, length, strand, opts)
));
},
|
addFeature
addFeature to Scribl chart/view and let Scribl manage track and lane placement to avoid overlaps
example:
chart.addFeature( new Rect('complex',3500, 2000) );
|
addFeature: function( feature ) {
var track = this.tracks[0] || this.addTrack();
track.addFeature(feature);
return feature;
},
|
slice
slices the Scribl chart/view at given places and returns a smaller chart/view
param: Int from - nucleotide position to slice from param: Int to - nucleotide position to slice to param: String type - inclusive (defaulte) includes any feature that has any part in region, exclusive, includes only features that are entirely in the region, strict if feature is partly in region, it'll cut that feature at the boundary and include the cut portion return: Object Scribl api: public
|
slice: function(from, to, type) {
type = type || 'inclusive';
var chart = this;
var sliced_features = [];
var numTracks = this.tracks.length;
var newChart = new Scribl(this.canvas, this.width);
newChart.scale.min = this.scale.min;
newChart.scale.max = this.scale.max;
newChart.offset = this.offset;
newChart.scale.off = this.scale.off;
newChart.scale.pretty = this.scale.pretty;
newChart.laneSizes = this.laneSizes;
newChart.drawStyle = this.drawStyle;
newChart.glyph = this.glyph;
newChart.glyphHooks = this.glyphHooks;
newChart.trackHooks = this.trackHooks;
newChart.previousDrawStyle = this.previousDrawStyle;
for ( var j=0; j < numTracks; j++) {
var track = this.tracks[j];
var newTrack = newChart.addTrack();
newTrack.drawStyle = track.drawStyle;
var numLanes = track.lanes.length;
for ( var i=0; i < numLanes; i++ ) {
var newLane = newTrack.addLane();
var s_features = track.lanes[i].features;
for (var k=0; k < s_features.length; k++ ) {
var end = s_features[k].position + s_features[k].length;
var start = s_features[k].position;
if(type == 'inclusive') {
if ( start >= from && start <= to )
newLane.addFeature( s_features[k].clone() )
else if ( end > from && end < to )
newLane.addFeature( s_features[k].clone() )
else if ( start < from && end > to )
newLane.addFeature( s_features[k].clone() )
else if ( start > from && end < to)
newLane.addFeature( s_features[k].clone() )
} else if (type == 'strict') {
if ( start >= from && start <= to){
if (end > from && end < to)
newLane.addFeature( s_features[k].clone() )
else {
if (s_features[k].glyphType == "BlockArrow" && s_features[k].strand == "+")
var f = s_features[k].clone("Rect");
else
var f = s_features[k].clone();
f.length = Math.abs(to - start);
newLane.addFeature( f );
}
} else if (end > from && end < to) {
if (s_features[k].glyphType == "BlockArrow" && s_features[k].strand == "-")
var f = s_features[k].clone("Rect");
else
var f = s_features[k].clone();
f.position = from;
f.length = Math.abs(end - from);
newLane.addFeature( f );
}
else if( start < from && end > to){
if (s_features[k].glyphType == "BlockArrow")
var f = s_features[k].clone("Rect");
else
var f = s_features[k].clone();
f.position = from;
f.length = Math.abs(to - from);
newLane.addFeature( f );
}
} else if (type == 'exclusive') {
if ( start >= from && start <= to && end > from && end < to)
newLane.addFeature( s_features[k].clone() )
}
}
}
}
return newChart;
},
|
draw
draws everything
|
draw: function() {
var ctx = this.ctx;
var tracks = this.tracks;
if (this.scrollable == true) {
this.initScrollable();
}
ctx.save();
this.initScale();
if (this.offset == undefined)
this.offset = Math.ceil( ctx.measureText('0').width/2 + 10 );
ctx.save();
for (var i=0; i<tracks.length; i++) {
if (!this.scale.off && this.scale.positions.indexOf(i) != -1)
this.drawScale();
tracks[i].draw();
}
if (!this.scale.off && this.scale.positions.indexOf(tracks.length) != -1)
this.drawScale();
ctx.restore();
ctx.restore();
if (!this.events.added)
this.registerEventListeners();
},
|
redraw
clears chart/view and draws it
|
redraw: function(){
this.ctx.clearRect(0,0,this.canvas.width, this.canvas.height);
if (this.tracks.length > 0)
this.draw();
},
|
initScale
initializes scale
|
initScale: function() {
if (this.scale.pretty) {
if (this.tick.auto) {
this.tick.major.size = this.determineMajorTick();
this.tick.minor.size = Math.round(this.tick.major.size / 10);
}
if (this.scale.auto) {
this.scale.min -= this.scale.min % this.tick.major.size;
this.scale.max = Math.round(this.scale.max / this.tick.major.size + .4)
* this.tick.major.size;
}
}
},
|
drawScale
draws scale
|
drawScale: function(options){
var firstMinorTick;
var ctx = this.ctx;
var fillStyleRevert = ctx.fillStyle;
if(options && options.init)
this.initScale();
var tickStartPos = this.scale.font.size + this.scale.size;
var majorTickEndPos = this.scale.font.size + 2;
var minorTickEndPos = this.scale.font.size + this.scale.size * 0.66;
var halfTickEndPos = this.scale.font.size + this.scale.size * 0.33;
ctx.font = this.scale.font.size + 'px arial';
ctx.textBaseline = 'top';
ctx.fillStyle = this.scale.font.color;
if (this.offset == undefined)
this.offset = Math.ceil( ctx.measureText('0').width/2 + 10 );
if (this.scale.min % this.tick.minor.size == 0)
firstMinorTick = this.scale.min
else
firstMinorTick = this.scale.min - (this.scale.min % this.tick.minor.size)
+ this.tick.minor.size;
for(var i = firstMinorTick; i <= this.scale.max; i += this.tick.minor.size){
ctx.beginPath();
if(i == 187250)
var h = 2;
var curr_pos = this.pixelsToNts(i - this.scale.min) + this.offset;
if ( i % this.tick.major.size == 0) {
var tickText = this.getTickText(i);
ctx.textAlign = 'center';
ctx.fillText( tickText , curr_pos, 0 );
ctx.moveTo( curr_pos, tickStartPos );
ctx.lineTo( curr_pos, majorTickEndPos );
ctx.strokeStyle = this.tick.major.color;
ctx.stroke();
} else {
ctx.moveTo( curr_pos, tickStartPos );
if ( i % (this.tick.major.size/2) == 0 ) {
ctx.strokeStyle = this.tick.halfColor;
ctx.lineTo( curr_pos, halfTickEndPos );
}
else{
ctx.strokeStyle = this.tick.minor.color;
ctx.lineTo( curr_pos, minorTickEndPos );
}
ctx.stroke();
}
}
ctx.fillStyle = fillStyleRevert;
ctx.translate(0, this.getScaleHeight() + this.laneBuffer);
},
|
pixelsToNts
Get the number of nucleotides per the given pixels
param: Int [pixels] optional - if not given, the ratio of pixels/nts will be returned return: Int nucleotides or pixels/nts ratio api: internal
|
pixelsToNts: function(pixels) {
if (pixels == undefined)
return ( this.width / ( this.scale.max - this.scale.min) );
else
return ( this.width / ( this.scale.max - this.scale.min) * pixels );
},
|
ntsToPixels
Get the number of pixels shown per given nucleotides
param: Int [nucleotides] optional - if not given, the ratio of nts/pixel will be returned return: Int pixels or nts/pixel ratio api: internal
|
ntsToPixels: function(nts) {
if (nts == undefined)
return ( 1 / this.pixelsToNts() );
else
return ( nts / this.width );
},
|
initScrollable
turns static chart into scrollable chart
|
initScrollable: function() {
var scrollStartMin;
if (!this.scrolled){
var parentDiv = document.createElement('div');
var canvasContainer = document.createElement('div');
var sliderDiv = document.createElement('div');
sliderDiv.id = 'scribl-zoom-slider';
sliderDiv.className = 'slider';
sliderDiv.style.cssFloat = 'left';
sliderDiv.style.height = (new String(this.canvas.height * .5)) + 'px';
sliderDiv.style.margin = '30px auto auto -20px'
parentDiv.style.cssText = this.canvas.style.cssText;
this.canvas.style.cssText = '';
parentWidth = parseInt(this.canvas.width) + 25;
parentDiv.style.width = parentWidth + 'px';
canvasContainer.style.width = this.canvas.width + 'px';
canvasContainer.style.overflow = 'auto';
canvasContainer.id = 'scroll-wrapper';
this.canvas.parentNode.replaceChild(parentDiv, this.canvas);
parentDiv.appendChild(sliderDiv);
canvasContainer.appendChild(this.canvas);
parentDiv.appendChild(canvasContainer);
jQuery(canvasContainer).dragscrollable({dragSelector: 'canvas:first', acceptPropagatedEvent: false});
}
var totalNts = this.scale.max - this.scale.min;
var scrollStartMax = this.scrollValues[1] || this.scale.max - totalNts * .35;
if( this.scrollValues[0] != undefined)
scrollStartMin = this.scrollValues[0];
else
scrollStartMin = this.scale.max + totalNts * .35;
var viewNts = scrollStartMax - scrollStartMin;
var viewNtsPerPixel = viewNts / document.getElementById('scroll-wrapper').style.width.split('px')[0];
var canvasWidth = (totalNts / viewNtsPerPixel) || 100;
this.canvas.width = canvasWidth;
this.width = canvasWidth - 30;
schart = this;
var zoomValue = (scrollStartMax - scrollStartMin) / (this.scale.max - this.scale.min) * 100 || 1;
jQuery(sliderDiv).slider({
orientation: 'vertical',
range: 'min',
min: 6,
max: 100,
value: zoomValue,
slide: function( event, ui ) {
var totalNts = schart.scale.max - schart.scale.min;
var width = ui['value'] / 100 * totalNts;
var widthPixels = ui['value'] / 100 * schart.canvas.width;
var canvasContainer = document.getElementById('scroll-wrapper');
var center = canvasContainer.scrollLeft + parseInt(canvasContainer.style.width.split('px')[0]) / 2;
var minPixel = center - widthPixels/2;
var maxPixel = center + widthPixels/2;
var min = schart.scale.min + (minPixel / schart.canvas.width) * totalNts;
var max = schart.scale.min + (maxPixel / schart.canvas.width) * totalNts;
schart.scrollValues = [min, max];
schart.ctx.clearRect(0, 0, schart.canvas.width, schart.canvas.height);
schart.draw();
}
});
var startingPixel = (scrollStartMin - this.scale.min) / totalNts * this.canvas.width;
document.getElementById('scroll-wrapper').scrollLeft = startingPixel;
this.scrolled = true;
},
|
determineMajorTick
intelligently determines a major tick interval based on size of the chart/view and size of the numbers on the scale
|
determineMajorTick: function() {
this.ctx.font = this.scale.font.size + 'px arial';
var numtimes = this.width/(this.ctx.measureText(this.getTickTextDecimalPlaces(this.scale.max)).width + this.scale.font.buffer);
var irregularTick = (this.scale.max - this.scale.min) / numtimes;
var baseNum = Math.pow(10, parseInt(irregularTick).toString().length -1);
this.tick.major.size = Math.ceil(irregularTick / baseNum) * baseNum;
var digits = (this.tick.major.size + '').length;
var places = Math.pow(10, digits);
var first_digit = this.tick.major.size / places;
if (first_digit > .1 && first_digit <= .5)
first_digit = .5;
else if (first_digit > .5)
first_digit = 1;
return (first_digit * places);
},
|
getTickText
abbreviates tick text numbers using 'k', or 'm' (e.g. 10000 becomes 10k)
|
getTickText: function(tickNumber) {
if ( !this.tick.auto )
return tickNumber;
var tickText = tickNumber;
if (tickNumber >= 1000000 ) {
var decPlaces = 5;
var base = Math.pow(10, decPlaces)
tickText = Math.round(tickText / 1000000 * base) / base + 'm';
} else if ( tickNumber >= 1000 ) {
var decPlaces = 2;
var base = Math.pow(10, decPlaces)
tickText = Math.round(tickText / 1000 * base) / base + 'k';
}
return tickText;
},
|
getTickTextDecimalPlaces
determines the tick text with decimal places
|
getTickTextDecimalPlaces: function(tickNumber){
if ( !this.tick.auto )
return tickNumber;
var tickText = tickNumber;
if (tickNumber >= 1000000 ) {
var decPlaces = 5;
tickText = Math.round( tickText / (1000000 / Math.pow(10,decPlaces)) ) + 'm';
} else if ( tickNumber >= 1000 ){
var decPlaces = 2;
tickText = Math.round( tickText / (1000 / Math.pow(10,decPlaces)) ) + 'k';
}
return tickText;
},
|
handleMouseEvent
handles mouse events
|
handleMouseEvent: function(e, type) {
this.myMouseEventHandler.setMousePosition(e);
var positionY = this.myMouseEventHandler.mouseY;
var lane;
for( var i=0; i < this.tracks.length; i++) {
for( var k=0; k < this.tracks[i].lanes.length; k++) {
var yt = this.tracks[i].lanes[k].getPixelPositionY();
var yb = yt + this.tracks[i].lanes[k].getHeight();
if (positionY >= yt && positionY <= yb ) {
lane = this.tracks[i].lanes[k];
break;
}
}
}
if (!lane) return;
var drawStyle = lane.track.getDrawStyle();
if (drawStyle == 'collapse') {
this.redraw();
} else if (drawStyle == 'line') {
} else {
this.ctx.save();
lane.erase();
this.ctx.translate(0, lane.getPixelPositionY());
lane.draw();
var ltt;
while (ltt = this.lastToolTips.pop() ) {
this.ctx.putImageData(ltt.pixels, ltt.x, ltt.y )
}
this.ctx.restore();
}
var chart = this;
if (type == 'click') {
var clicksFns = chart.events.clicks;
for (var i = 0; i < clicksFns.length; i++)
clicksFns[i](chart);
} else {
var mouseoverFns = chart.events.mouseovers;
for (var i = 0; i < mouseoverFns.length; i++)
mouseoverFns[i](chart);
}
this.myMouseEventHandler.reset(chart);
},
|
addClickEventListener
add's function that will execute each time a feature is clicked
|
addClickEventListener: function(func) {
this.events.clicks.push(func);
},
|
addMouseoverEventListener
add's function that will execute each time a feature is mouseovered
|
addMouseoverEventListener: function(func) {
this.events.mouseovers.push(func);
},
|
removeEventListeners
remove event listerners
|
removeEventListeners: function(eventType){
if (eventType == 'mouseover')
this.canvas.removeEventListener('mousemove', this.mouseHandler);
else if (eventType == 'click')
this.canvas.removeEventListener('click', this.clickHandler);
},
|
registerEventListeners
adds event listerners
|
registerEventListeners: function() {
var chart = this;
if ( this.events.mouseovers.length > 0) {
this.canvas.removeEventListener('mousemove', chart.mouseHandler);
this.canvas.addEventListener('mousemove', chart.mouseHandler, false);
}
if ( this.events.clicks.length > 0 ) {
this.canvas.removeEventListener('click', chart.clickHandler);
this.canvas.addEventListener('click', chart.clickHandler, false);
}
this.events.added = true;
}
});
|
| Scribl.track.js |
Scribl:Track
Tracks are used to segregrate different sequence data
Chase Miller 2011
|
var Track = Class.extend({
|
init
Constructor
This is called with new Track() , but to create new Tracks associated with a chart use Scribl.addTrack()
|
init: function(chart) {
var track = this;
this.chart = chart
this.lanes = [];
this.ctx = chart.ctx;
this.uid = _uniqueId('track');
this.drawStyle = undefined;
this.hide = false;
this.hooks = {};
for (var i=0; i<chart.trackHooks.length; i++) {
this.addDrawHook( chart.trackHooks[i] );
}
this.coverageData = [];
this.maxDepth = 0;
},
|
addLane
creates a new Lane associated with this Track
|
addLane: function() {
var lane = new Lane(this.ctx, this);
this.lanes.push(lane);
return lane;
},
|
addGene
syntactic sugar function to add a feature with the gene type to this Track
param: Int position - start position of the gene param: Int length - length of the gene param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of options that can be applied to gene return: Object gene - a feature with the 'gene' type api: public
|
addGene: function(position, length, strand, opts) {
return (this.addFeature( new BlockArrow("gene", position, length, strand, opts) ) );
},
|
addProtein
syntactic sugar function to add a feature with the protein type to this Track
param: Int position - start position of the protein param: Int length - length of the protein param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of options that can be applied to protein return: Object protein - a feature with the 'protein' type api: public
|
addProtein: function(position, length, strand, opts) {
return (this.addFeature( new BlockArrow("protein", position, length, strand, opts) ) );
},
|
addFeature
addFeature to this Track and let Scribl manage lane placement to avoid overlaps
example:
track.addFeature( new Rect('complex',3500, 2000) );
param: Object feature - any of the derived Glyph classes (e.g. Rect, Arrow, etc..) return: Object feature - new feature api: public
|
addFeature: function( feature ) {
var curr_lane;
var new_lane = true;
for (var j=0; j < this.lanes.length; j++) {
var prev_feature = this.lanes[j].features[ this.lanes[j].features.length - 1 ];
var spacer = 3/this.chart.pixelsToNts() || 3;
if ( prev_feature != undefined && (feature.position - spacer) > (prev_feature.position + prev_feature.length) ) {
new_lane = false;
curr_lane = this.lanes[j];
break;
}
}
if (new_lane)
curr_lane = this.addLane();
curr_lane.addFeature( feature );
return feature;
},
|
hide
hides the track so it doesn't get drawn
|
hide: function() {
this.hide = true;
},
|
unhide
unhides the track so it is drawn
|
unhide: function() {
this.hide = false;
},
|
getDrawStyle
returns the draw style associated with this track
|
getDrawStyle: function() {
if (this.drawStyle)
return this.drawStyle
else
return this.chart.drawStyle;
},
|
getHeight
returns the height of this track in pixels
return: Int height api: public
|
getHeight: function() {
var wholeHeight = 0;
var numLanes = this.lanes.length;
var laneBuffer = this.chart.laneBuffer;
var drawStyle = this.getDrawStyle();
if (drawStyle == 'line' || drawStyle == 'collapse')
numLanes = 1;
for (var i=0; i < numLanes; i++) {
wholeHeight += laneBuffer;
wholeHeight += this.lanes[i].getHeight();
}
wholeHeight -= laneBuffer;
return wholeHeight;
},
|
getPixelPositionY
gets the number of pixels from the top of the chart to the top of this track
|
getPixelPositionY: function() {
var track = this;
var y;
if (!track.chart.scale.off)
y = track.chart.getScaleHeight() + track.chart.laneBuffer;
else
y = 0;
for( var i=0; i < track.chart.tracks.length; i++ ) {
if (track.uid == track.chart.tracks[i].uid) break;
y += track.chart.trackBuffer;
y += track.chart.tracks[i].getHeight();
}
return y;
},
|
calcCoverageData
calculates the coverage (the number of features) at each pixel
|
calcCoverageData: function() {
var lanes = this.lanes
var min = this.chart.scale.min;
var max = this.chart.scale.max;
for (var i=0; i<lanes.length; i++) {
for (var k=0; k<lanes[i].features.length; k++) {
var feature = lanes[i].features[k];
var pos = feature.position;
var end = feature.getEnd();
if ( (pos >= min && pos <= max) || (end >= min && end <= max) ) {
var from = Math.round( feature.getPixelPositionX() );
var to = Math.round( from + feature.getPixelLength() );
for (var j=from; j <= to; j++) {
this.coverageData[j] = this.coverageData[j] + 1 || 1;
this.maxDepth = Math.max(this.coverageData[j], this.maxDepth);
}
}
}
}
},
|
erase
erases this track
|
erase: function() {
var track = this;
track.chart.ctx.clearRect(0, track.getPixelPositionY(), track.chart.width, track.getHeight());
},
|
draw
draws Track
|
draw: function() {
var track = this;
var dontDraw = false;
for (var i in track.hooks) {
dontDraw = track.hooks[i](track) || dontDraw;
}
if ( track.status == 'waiting' ) {
track.drawOnResponse = true;
return;
}
if(track.hide)
return;
var style = track.getDrawStyle();
var laneSize = track.chart.laneSizes;
var lanes = track.lanes;
var laneBuffer = track.chart.laneBuffer;
var trackBuffer = track.chart.trackBuffer;
var y = laneSize + trackBuffer;
var ctx = track.chart.ctx;
if (!dontDraw) {
if ( style == undefined || style == 'expand' ) {
for (var i=0; i<lanes.length; i++) {
lanes[i].y = y;
if(lanes[i].draw()) {
var height = lanes[i].getHeight();
ctx.translate(0, height + laneBuffer);
y = y + height + laneBuffer;
}
}
} else if ( style == 'collapse' ) {
var features = []
for (var i=0; i<lanes.length; i++) {
features = features.concat(lanes[i].features);
}
features.sort( function(a,b){ return(a.position - b.position); } );
for (var j=0; j<features.length; j++) {
var originalLength = features[j].length;
var originalName = features[j].name;
var m = undefined;
features[j].draw();
features[j].length = originalLength;
features[j].name = originalName;
}
if (lanes.length > 0)
ctx.translate(0, lanes[0].getHeight() + laneBuffer);
} else if ( style == 'line' ) {
track.coverageData = [];
if (track.coverageData.length == 0) track.calcCoverageData();
var normalizationFactor = this.maxDepth;
ctx.beginPath();
for (var k=this.chart.offset; k <= this.chart.width + this.chart.offset; k++) {
var normalizedPt = track.coverageData[k] / normalizationFactor * laneSize || 0;
normalizedPt = laneSize - normalizedPt;
ctx.lineTo(k, normalizedPt);
}
ctx.lineTo(this.chart.width + this.chart.offset, laneSize)
ctx.stroke();
ctx.translate(0, lanes[0].getHeight() + laneBuffer);
}
}
ctx.translate(0,trackBuffer-laneBuffer);
},
|
addDrawHook
add function that executes before the track is drawn
param: Function function - takes track as param, returns true to stop the normal draw, false to allow return: Int id - returns the uniqe id for the hook which is used to remove it api: public
|
addDrawHook: function(fn, hookId) {
var uid = hookId || _uniqueId('drawHook');
this.hooks[uid] = fn;
return uid;
},
|
removeDrawHook
removes function that executes before the track is drawn
|
removeDrawHook: function(uid) {
delete this.hooks[uid];
}
});
|
| Scribl.lane.js |
Scribl::Lane
A lane is used to draw features on a single y position
Chase Miller 2011
|
var Lane = Class.extend({
|
init
Constructor
This is called with new Lane() , but to create new Lanes associated with a chart use track.addLane()
|
init: function(ctx, track) {
this.height = undefined;
this.features = [];
this.ctx = ctx;
this.track = track;
this.chart = track.chart;
this.uid = _uniqueId('lane');
},
|
addGene
syntactic sugar function to add a feature with the gene type to this Lane
param: Int position - start position of the gene param: Int length - length of the gene param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of options that can be applied to gene return: Object gene - a feature with the 'gene' type api: public
|
addGene: function(position, length, strand, opts) {
return (this.addFeature( new BlockArrow("gene", position, length, strand, opts) ) );
},
|
addProtein
syntactic sugar function to add a feature with the protein type to this Lane
param: Int position - start position of the protein param: Int length - length of the protein param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of options that can be applied to protein return: Object protein - a feature with the 'protein' type api: public
|
addProtein: function(position, length, strand, opts) {
return (this.addFeature( new BlockArrow("protein", position, length, strand, opts) ) );
},
|
addFeature
addFeature to this Lane, allowing potential overlaps
example:
lane.addFeature( new Rect('complex',3500, 2000) );
param: Object feature - any of the derived Glyph classes (e.g. Rect, Arrow, etc..) return: Object feature - new feature api: public
|
addFeature: function( feature ) {
feature.lane = this;
this.features.push(feature);
if (! this.chart[feature.type] ){
this.chart[feature.type] = {'text': {}}
}
if ( feature.length + feature.position > this.chart.scale.max || !this.chart.scale.max )
this.chart.scale.max = feature.length + feature.position;
if ( feature.position < this.chart.scale.min || !this.chart.scale.min )
this.chart.scale.min = feature.position;
return feature;
},
|
loadFeatures
adds the features to this Lane
param: Array features - array of features, which can be any of the derived Glyph classes (e.g. Rect, Arrow, etc..) api: public
|
loadFeatures: function(features) {
var featureNum = features.length;
for(var i=0; i<featureNum; i++)
this.addFeature(features[i]);
},
|
getHeight
returns the height of this lane in pixels
return: Int height api: public
|
getHeight: function() {
if ( this.height != undefined )
return this.height;
else
return this.chart.laneSizes;
},
|
getPixelPositionY
gets the number of pixels from the top of the chart to the top of this lane
|
getPixelPositionY: function() {
var lane = this;
var y = lane.track.getPixelPositionY();
var laneHeight = lane.getHeight();
for( var i=0; i < lane.track.lanes.length; i++ ) {
if (lane.uid == lane.track.lanes[i].uid) break;
y += lane.track.chart.laneBuffer;
y += laneHeight;
}
return y;
},
|
erase
erases this lane
|
erase: function() {
var lane = this;
lane.chart.ctx.clearRect(0, lane.getPixelPositionY(), lane.track.chart.canvas.width, lane.getHeight());
},
|
draw
draws lane
|
draw: function() {
var min = this.track.chart.scale.min;
var max = this.track.chart.scale.max;
var hasGlyphs = false;
for (var i=0; i< this.features.length; i++) {
var pos = this.features[i].position;
var end = this.features[i].getEnd();
if ( pos >= min && pos <= max || end >= min && end <= max) {
this.features[i].draw();
hasGlyphs = true;
}
}
return hasGlyphs;
}
});
|
| Scribl.glyph.js |
Scribl::Glyph
Generic glyph class that should not be used directly.
All feature classes (e.g. Rect, arrow, etc..) inherit
from this class
Chase Miller 2011
|
var Glyph = Class.extend({
|
init
Constructor, call this with new Glyph()
This method must be called in all feature subclasses like so this._super(type, pos, length, strand, opts)
param: String type - a tag to associate this glyph with param: Int position - start position of the glyph param: Int length - length of the glyph param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of attributes that can be applied to glyph api: internal
|
init: function(type, pos, length, strand, opts) {
var glyph = this;
this.uid = _uniqueId('feature');
glyph.position = pos;
glyph.length = length;
glyph.strand = strand;
this.type = type;
glyph.opts = {};
glyph.name = "";
glyph.borderColor = "none";
glyph.borderWidth = undefined;
glyph.ntLevel = 4;
glyph.tooltips = [];
glyph.hooks = {};
glyph.addDrawHook(function(theGlyph) {
if (theGlyph.ntLevel != undefined && theGlyph.seq && theGlyph.lane.chart.ntsToPixels() < theGlyph.ntLevel){
var s = new Seq(theGlyph.type, theGlyph.position, theGlyph.length, theGlyph.seq, theGlyph.opts);
s.lane = theGlyph.lane;
s.ctx = theGlyph.ctx;
s._draw();
return true;
}
return false;
}, "ntHook");
glyph.text = {};
glyph.text.font = undefined;
glyph.text.size = undefined;
glyph.text.color = undefined;
glyph.text.align = undefined;
glyph.onClick = undefined;
glyph.onMouseover = undefined;
for (var attribute in opts) {
glyph[attribute] = opts[attribute];
glyph.opts[attribute] = opts[attribute];
}
},
|
setColorGradient
creates a gradient given a list of colors
|
setColorGradient: function() {
if(arguments.length == 1){
this.color = arguments[0];
return;
}
var lineargradient = this.lane.ctx.createLinearGradient(this./2, 0, this.length/2, this.getHeight());
var color;
for(var i=0; color=arguments[i], i < arguments.length; i++){
lineargradient.addColorStop(i / (arguments.length-1), color);
}
this.color = lineargradient;
},
|
getPixelLength
gets the length of the glyph/feature in pixels
|
getPixelLength: function() {
var glyph = this;
return ( glyph.lane.chart.pixelsToNts(glyph.length) || 1 );
},
|
getPixelPositionx
gets the number of pixels from the left of the chart to the left of this glyph/feature
|
getPixelPositionX: function() {
var glyph = this;
var offset = parseInt(glyph.lane.track.chart.offset) || 0;
if (glyph.parent)
var position = glyph.position + glyph.parent.position - glyph.lane.track.chart.scale.min;
else
var position = glyph.position - glyph.lane.track.chart.scale.min;
return ( glyph.lane.track.chart.pixelsToNts( position ) + offset);
},
|
getPixelPositionY
gets the number of pixels from the top of the chart to the top of this glyph/feature
|
getPixelPositionY : function() {
var glyph = this;
return (glyph.lane.getPixelPositionY());
},
|
getEnd
gets the nucleotide/amino acid end point of this glyph/feature
|
getEnd: function() {
return (this.position + this.length);
},
|
clone
shallow copy
|
clone: function(glyphType) {
var glyph = this;
var newFeature;
glyphType = glyphType || glyph.glyphType;
if (glyphType == "Rect" || glyphType == "Line")
glyph.strand = undefined
if(glyph.strand){
var str = 'new ' + glyphType + '("' + glyph.type + '",' + glyph.position + ',' + glyph.length + ',"' + glyph.strand + '",' + JSON.stringify(glyph.opts) + ')';
newFeature = eval( str );
var attrs = Object.keys(glyph);
for ( var i=0; i < attrs.length; i++) {
newFeature[attrs[i]] = glyph[attrs[i]];
}
} else {
var str = 'new ' + glyphType + '("' + glyph.type + '",' + glyph.position + ',' + glyph.length + ',' + JSON.stringify(glyph.opts) + ')';
newFeature = eval(str);
var attrs = Object.keys(glyph);
for ( var i=0; i < attrs.length; i++) {
newFeature[attrs[i]] = glyph[attrs[i]];
}
}
newFeature.tooltips = glyph.tooltips;
newFeature.hooks = glyph.hooks;
return( newFeature );
},
|
getAttr
determine and retrieve the appropriate value for each attribute, checks parent, default, type, and glyph levels in the appropriate order
param: * attribute return: * attribute api: public
|
getAttr : function(attr) {
var glyph = this;
var attrs = attr.split('-');
var glyphLevel = glyph;
for( var k=0; k < attrs.length; k++) { glyphLevel = glyphLevel[attrs[k]]; }
if (glyphLevel) return glyphLevel
if (glyph.parent) {
var parentLevel = glyph.parent;
for( var k=0; k < attrs.length; k++) { parentLevel = parentLevel[attrs[k]]; }
if (parentLevel) return parentLevel;
}
var typeLevel = this.lane.chart[glyph.type];
if (typeLevel) {
for( var k=0; k < attrs.length; k++) { typeLevel = typeLevel[attrs[k]]; }
if (typeLevel) return typeLevel;
}
var chartLevel = glyph.lane.chart.glyph;
for( var k=0; k < attrs.length; k++) { chartLevel = chartLevel[attrs[k]]; }
if (chartLevel) return chartLevel;
return undefined;
},
|
drawText
draws the text for a glyph/feature
##
param: String text api: internal
|
drawText : function(text) {
var glyph = this;
var ctx = glyph.lane.chart.ctx;
var padding = 5;
var length = glyph.getPixelLength();
var height = glyph.getHeight();
var fontSize = glyph.getAttr('text-size');
var fontSizeMin = 8;
var fontStyle = glyph.getAttr('text-style');
ctx.font = fontSize + "px " + fontStyle;
ctx.textBaseline = "middle";
ctx.fillStyle = glyph.getAttr('text-color');
var placement = undefined
var align = glyph.getAttr('text-align');
if ( align == "start")
if ( glyph.strand == '+' )
align = 'left';
else
align = 'right';
else if ( align == "end" )
if ( glyph.strand == '+' )
align = 'right';
else
align = 'left';
ctx.textAlign = align;
if (align == 'left')
placement = 0 + padding;
else if ( align == 'center' )
placement = length/2;
else if ( align == "right" )
placement = length - padding;
var dim = ctx.measureText(text);
if (text && text != "") {
while ( (length-dim.width) < 4 ) {
fontSize = /^\d+/.exec(ctx.font);
fontSize--;
dim = ctx.measureText(text);
ctx.font = fontSize + "px " + fontStyle;
if (fontSize <= fontSizeMin) {
text = "";
break;
}
}
if (glyph.glyphType == "Complex") {
var offset = 0;
var fontsize = /^\d+/.exec(ctx.font);
if (align == "center")
offset = -(ctx.measureText(text)./2 + padding/2);
ctx.clearRect(placement + offset, /2 - fontsize/2, ctx.measureText(text).width + padding, fontsize);
}
ctx.fillText(text, placement, height/2);
}
},
|
calcRoundness
determines a roundness value based on the height of the glyph feature, so roundness looks consistent as lane size changes
return: Int roundness api: internal
|
calcRoundness : function() {
var roundness = this.getHeight() * this.getAttr('roundness')/100;
roundness = ((roundness*10 % 5) >= 2.5 ? parseInt(roundness*10 / 5) * 5 + 5 : parseInt(roundness*10 / 5) * 5) / 10;
return (roundness);
},
|
isContainedWithinRect
determines if this glyph/feature is contained within a box with the given coordinates
param: Int selectionTlX - top left X coordinate of bounding box param: Int selectionTlY - top left Y coordinate of bounding box param: Int selectionBrX - bottom right X coordinate of bounding box param: Int selectionBrY - bottom right Y coordinate of bounding box return: Boolean isContained api: public
|
isContainedWithinRect : function(selectionTlX, selectionTlY, selectionBrX, selectionBrY) {
var glyph = this;
var y = glyph.getPixelPositionY();
var tlX = glyph.getPixelPositionX();
var tlY = y
var brX = glyph.getPixelPositionX() + glyph.getPixelLength();
var brY = y + glyph.getHeight();
return tlX >= selectionTlX
&& brX <= selectionBrX
&& tlY >= selectionTlY
&& brY <= selectionBrY;
},
|
getHeight
returns the height of this glyph/feature in pixels
return: Int height api: public
|
getHeight : function() {
var glyph = this;
return ( glyph.lane.getHeight() );
},
|
getFillStyle
converts glyph.color into the format taken by canvas.context.fillStyle
|
getFillStyle : function() {
var glyph = this;
var color = glyph.getAttr('color');
if (color instanceof Array) {
var lineargradient = this.lane.track.chart.ctx.createLinearGradient(this./2, 0, this.length/2, this.getHeight());
var currColor;
for(var i=0; currColor=color[i], i < color.length; i++)
lineargradient.addColorStop(i / (color.length-1), currColor);
return lineargradient
} else if ( color instanceof Function) {
var lineargradient = this.lane.track.chart.ctx.createLinearGradient(this./2, 0, this.length/2, this.getHeight());
return color(lineargradient);
} else
return color;
},
|
getStrokeStyle
converts glyph.borderColor into the format taken by canvas.context.fillStyle
|
getStrokeStyle : function() {
var glyph = this;
var color = glyph.getAttr('borderColor');
if (typeof(color) == "object") {
var lineargradient = this.lane.ctx.createLinearGradient(this./2, 0, this.length/2, this.getHeight());
var currColor;
for(var i=0; currColor=color[i], i < color.length; i++)
lineargradient.addColorStop(i / (color.length-1), currColor);
return lineargradient
} else
return color;
},
|
isSubFeature
checks if glyph/feature has a parent
|
isSubFeature: function() {
return (this.parent != undefined);
},
|
erase
erase this glyph/feature
|
erase: function() {
var glyph = this;
glyph.ctx.save();
glyph.ctx.setTransform(1,0,0,1,0,0);
glyph.ctx.clearRect(glyph.getPixelPositionX(), glyph.getPixelPositionY(), glyph.getPixelLength(), glyph.getHeight());
glyph.ctx.restore();
},
|
addDrawHook
add function to glyph that executes before the glyph is drawn
param: Function function - takes glyph as param, returns true to stop the normal draw, false to allow return: Int id - returns the uniqe id for the hook which is used to remove it api: public
|
addDrawHook: function(fn, hookId) {
var uid = hookId || _uniqueId('drawHook');
this.hooks[uid] = fn;
return uid;
},
|
removeDrawHook
removes function to glyph that executes before the glyph is drawn
|
removeDrawHook: function(uid) {
delete this.hooks[uid];
},
|
addTooltip
add tooltip to glyph. Can add multiple tooltips
param: Int placement - two options 'above' glyph or 'below' glyph param: Int verticalOffset - + numbers for up, - for down param: Hash options - optional attributes, horizontalOffset and ntOffset (nucleotide) return: Object tooltip api: public
|
addTooltip: function(text, placement, verticalOffset, opts){
var glyph = this;
var tt = new Tooltip(text, placement, verticalOffset, opts);
tt.feature = glyph;
glyph.tooltips.push( tt );
},
|
fireTooltips
draws the tooltips associated with this feature
|
fireTooltips: function() {
for (var i=0; i < this.tooltips.length; i++)
this.tooltips[i].fire()
},
|
draw
draws the glyph
|
draw: function() {
var glyph = this;
glyph.ctx = glyph.lane.chart.ctx;
glyph.ctx.beginPath();
var fontSize = /^\d+/.exec(glyph.ctx.font);
var font = /\S+$/.exec(glyph.ctx.font);
var fontSizeMin = 10;
glyph.onClick = glyph.getAttr('onClick');
glyph.onMouseover = glyph.getAttr('onMouseover');
glyph.ctx.fillStyle = glyph.getFillStyle();
var fillStyle = glyph.ctx.fillStyle;
var position = glyph.getPixelPositionX();
var height = glyph.getHeight();
(height < fontSizeMin) ? glyph.ctx.font = fontSizeMin + "px " + font : glyph.ctx.font = height *.9 + "px " + font;
glyph.ctx.translate(position, 0);
if (glyph.strand == '-' && !glyph.isSubFeature())
glyph.ctx.transform(-1, 0, 0, 1, glyph.getPixelLength(), 0);
var dontDraw = false;
for (var i in glyph.hooks) {
dontDraw = glyph.hooks[i](glyph) || dontDraw;
}
if (!dontDraw) {
glyph._draw();
}
if (glyph.borderColor != "none") {
if(glyph.color == 'none' && glyph.parent.glyphType == 'Complex') {
glyph.erase();
}
var saveStrokeStyle = glyph.ctx.strokeStyle;
var saveLineWidth = glyph.ctx.lineWidth;
glyph.ctx.strokeStyle = glyph.getStrokeStyle();
glyph.ctx.lineWidth = glyph.getAttr('borderWidth');
glyph.ctx.stroke();
glyph.ctx.strokeStyle = saveStrokeStyle;
glyph.ctx.lineWidth = saveLineWidth;
}
if (glyph.color !="none") glyph.ctx.fill();
if (glyph.strand == '-' && !glyph.isSubFeature())
glyph.ctx.transform(-1, 0, 0, 1, glyph.getPixelLength(), 0);
glyph.drawText(glyph.getAttr('name'));
glyph.ctx.translate(-position, 0);
glyph.ctx.fillStyle = fillStyle;
glyph.lane.chart.myMouseEventHandler.addEvents(this);
},
|
redraw
erases this specific glyph and redraws it
|
redraw: function() {
var glyph = this;
glyph.lane.ctx.save();
glyph.erase;
var y = glyph.getPixelPositionY();
glyph.lane.ctx.translate(0, y);
glyph.draw();
glyph.lane.ctx.restore();
}
});
|
| Scribl.events.js |
Scribl::Events
Adds event support to Scribl
Chase Miller 2011
|
var MouseEventHandler = Class.extend({
|
init
Constructor, call this with new MouseEventHandler()
|
init: function(chart) {
this.chart = chart;
this.mouseX = null;
this.mouseY = null;
this.eventElement = undefined;
this.isEventDetected = false;
this.tooltip = new Tooltip("", 'above', -4);
},
|
addEvents
registers event listeners if feature (or parent if part of complex feature) has mouse events associated with it
|
addEvents: function(feature) {
var chart = this.chart;
var ctx = chart.ctx;
var me = chart.myMouseEventHandler;
if (feature.onMouseover && !chart.events.hasMouseover ) {
chart.addMouseoverEventListener(chart.myMouseEventHandler.handleMouseover);
chart.events.hasMouseover = true;
}
else if (feature.tooltips.length>0 && !chart.events.hasMouseover){
chart.addMouseoverEventListener(chart.myMouseEventHandler.handleMouseover);
chart.events.hasMouseover = true;
}
else if (feature.parent && feature.parent.tooltips.length>0 && !chart.events.hasMouseover){
chart.addMouseoverEventListener(chart.myMouseEventHandler.handleMouseover);
chart.events.hasMouseover = true;
}
else if (feature.parent && feature.parent.onMouseover && !chart.events.hasMouseover) {
chart.addMouseoverEventListener(chart.myMouseEventHandler.handleMouseover);
chart.events.hasMouseover = true;
}
if (feature.onClick && !chart.events.hasClick) {
chart.addClickEventListener(chart.myMouseEventHandler.handleClick);
chart.addMouseoverEventListener(chart.myMouseEventHandler.handleMouseStyle);
chart.events.hasClick = true;
} else if (feature.parent && feature.parent.onClick && !chart.events.hasClick) {
chart.addClickEventListener(chart.myMouseEventHandler.handleClick);
chart.addMouseoverEventListener(chart.myMouseEventHandler.handleMouseStyle);
chart.events.hasClick = true;
}
if (!me.isEventDetected && ctx.isPointInPath_mozilla(me.mouseX,me.mouseY)) {
me.eventElement = feature;
me.isEventDetected = true;
}
},
|
setMousePosition
sets the mouse position relative to the canvas
param: Object e - event api: internal
|
setMousePosition: function(e) {
if (e!=null) {
var rect = this.chart.canvas.getBoundingClientRect();
this.mouseX = e.clientX - rect.left;
this.mouseY = e.clientY - rect.top;
}
},
|
handleClick
gets called when there is a click and determines how to handle it
|
handleClick: function(chart) {
var me = chart.myMouseEventHandler;
var clicked = me.eventElement;
var onClick;
if (clicked != undefined && clicked.onClick != undefined)
onClick = clicked.onClick
else if (clicked && clicked.parent && clicked.parent.onClick)
onClick = clicked.parent.onClick
if(onClick){
if (typeof(onClick) == "string"){ window.open(onClick); }
else if (typeof(onClick) == "function"){ onClick(clicked); }
}
},
|
handleMouseover
gets called when there is a mouseover and fires tooltip if necessary
|
handleMouseover: function(chart) {
var me = chart.myMouseEventHandler;
var clicked = me.eventElement;
if (clicked && clicked.onMouseover == undefined && clicked.parent && clicked.parent.onMouseover) {
clicked.onMouseover = clicked.parent.onMouseover
}
if(clicked && clicked.onMouseover) {
if (typeof(clicked.onMouseover) == "string"){ me.tooltip.fire(clicked); }
else if (typeof(clicked.onMouseover) == "function"){ clicked.onMouseover(clicked); }
}
if (clicked && clicked.tooltips.length > 0)
clicked.fireTooltips();
},
|
handleMouseStyle
changes cursor to pointer if the feature the mouse is over can be clicked
|
handleMouseStyle: function(chart) {
var me = chart.myMouseEventHandler;
var obj = me.eventElement;
var ctx = chart.ctx;
if (obj && obj.onClick != undefined)
ctx.canvas.style.cursor = 'pointer';
else if (obj && obj.parent && obj.parent.onClick != undefined)
ctx.canvas.style.cursor = 'pointer';
else
ctx.canvas.style.cursor = 'auto';
},
|
reset
resets the state of the mouseEventHandler
|
reset: function(chart) {
var me = chart.myMouseEventHandler;
me.mouseX = null;
me.mouseY = null;
me.eventElement = undefined;
me.isEventDetected = null;
me.elementIndexCounter = 0;
}
});
CanvasRenderingContext2D.prototype.isPointInPath_mozilla = function( x, y )
{
if (navigator.userAgent.indexOf('Firefox') != -1){
this.save();
this.setTransform( 1, 0, 0, 1, 0, 0 );
var ret = this.isPointInPath( x, y );
this.restore();
} else
var ret = this.isPointInPath( x, y );
return ret;
}
|
| Scribl.tooltips.js |
Scribl::Tooltips
Adds event support to Scribl
Chase Miller 2011
|
var Tooltip = Class.extend({
|
init
Constructor, call this with new tooltips()
|
init: function(text, placement, verticalOffset, opts) {
var tt = this;
tt.text = text;
tt.placement = placement || 'above';
tt.verticalOffset = verticalOffset || 0;
for (var attribute in opts)
tt[attribute] = opts[attribute];
tt.horizontalOffset = tt.horizontalOffset || 0;
tt.ntOffset = tt.ntOffset || 0;
},
|
fire
fires the tooltip
|
fire: function(ft) {
var feature = ft || this.feature;
this.chart = feature.lane.track.chart;
this.ctx = this.chart.ctx;
this.draw(feature, 1);
},
|
draw
draws tooltip
|
draw: function(feature, opacity) {
this.ctx.globalAlpha = opacity;
var roundness = this.chart.tooltips.roundness;
var font = this.chart.tooltips.text.font;
var fontSize = this.chart.tooltips.text.size;
var text = this.text || feature.onMouseover;
this.ctx.save();
this.ctx.font = fontSize + "px " + font;
var dim = this.ctx.measureText(text);
var textlines = [text];
var height = fontSize + 10;
var length = dim.width + 10;
var vertical_offset = height - 4;
var fillStyle;
var strokeStyle;
var ntOffsetPx = 0;
if(feature.seq) {
var lengthPx = feature.getPixelLength();
ntOffsetPx = this.ntOffset * (lengthPx / feature.length);
}
var x = feature.getPixelPositionX() + this.horizontalOffset + ntOffsetPx;
var y;
if (this.placement == 'below')
y = feature.getPixelPositionY() + feature.getHeight() - this.verticalOffset;
else
y = feature.getPixelPositionY() - height - this.verticalOffset;
var geneLength = feature.getPixelLength();
var mincols = 200;
if (length > mincols) {
var charpixel = this.ctx.measureText("s").width;
var max = parseInt(mincols / charpixel);
var formattedText = ScriblWrapLines(max, text);
length = mincols + 10;
height = formattedText[1]*fontSize + 10;
textlines = formattedText[0];
}
if (length + x > this.chart.width)
x = this.chart.width - length;
if ( this.chart.tooltips.style == "light" ) {
fillStyle = this.chart.ctx.createLinearGradient(x + /2, y, x + length/2, y + height);
fillStyle.addColorStop(0,'rgb(253, 248, 196)');
fillStyle.addColorStop(.75,'rgb(253, 248, 196)');
fillStyle.addColorStop(1,'white');
strokeStyle = this.chart.ctx.createLinearGradient(x + /2, y, x + length/2, y + height);
strokeStyle.addColorStop(0,'black');
strokeStyle.addColorStop(1,'rgb(64, 64, 64)');
this.chart.tooltips.text.color = "black";
} else if ( this.chart.tooltips.style == "dark" ) {
fillStyle = this.chart.ctx.createLinearGradient(x + /2, y, x + length/2, y + height);
fillStyle.addColorStop(0,'rgb(64, 64, 64)');
fillStyle.addColorStop(1,'rgb(121, 121, 121)');
strokeStyle = "white";
this.chart.tooltips.text.color = "white";
}
this.chart.lastToolTips.push( {
'pixels' : this.ctx.getImageData(x-1, y-1, length+2, height+2),
'x' : x-1,
'y' : y-1
});
this.ctx.fillStyle = fillStyle;
this.ctx.beginPath();
tlc_ctrl_x = x;
tlc_ctrl_y = y;
tlc_lgth_x = x + roundness;
tlc_lgth_y = y;
tlc_wdth_x = x;
tlc_wdth_y = y + roundness;
blc_ctrl_x = x;
blc_ctrl_y = y + height;
blc_lgth_x = x + roundness;
blc_lgth_y = y + height;
blc_wdth_x = x;
blc_wdth_y = y + height - roundness;
brc_ctrl_x = x + length;
brc_ctrl_y = y + height;
brc_lgth_x = x + length - roundness;
brc_lgth_y = y + height;
brc_wdth_x = x + length;
brc_wdth_y = y + height - roundness;
trc_ctrl_x = x + length;
trc_ctrl_y = y;
trc_lgth_x = x + length - roundness;
trc_lgth_y = y;
trc_wdth_x = x + length;
trc_wdth_y = y + roundness;
this.ctx.moveTo(tlc_lgth_x, tlc_lgth_y);
this.ctx.quadraticCurveTo(tlc_ctrl_x, tlc_ctrl_y, tlc_wdth_x, tlc_wdth_y);
this.ctx.lineTo(blc_wdth_x, blc_wdth_y);
this.ctx.quadraticCurveTo(blc_ctrl_x, blc_ctrl_y, blc_lgth_x, blc_lgth_y);
this.ctx.lineTo(brc_lgth_x, brc_lgth_y);
this.ctx.quadraticCurveTo(brc_ctrl_x, brc_ctrl_y, brc_wdth_x, brc_wdth_y);
this.ctx.lineTo(trc_wdth_x, trc_wdth_y);
this.ctx.quadraticCurveTo(trc_ctrl_x, trc_ctrl_y, trc_lgth_x, trc_lgth_y);
this.ctx.lineTo(tlc_lgth_x, tlc_lgth_y);
this.ctx.fill();
this.ctx.lineWidth = this.chart.tooltips.borderWidth;
this.ctx.strokeStyle = strokeStyle;
this.ctx.stroke();
this.ctx.textBaseline = "middle";
this.ctx.fillStyle = this.chart.tooltips.text.color;
for (var i=0; i < textlines.length; i++) {
var dim = this.ctx.measureText(textlines[i]);
this.ctx.fillText(textlines[i], x + 5 , y + fontSize*(i+1));
}
this.ctx.restore();
}
});
|
| Scribl.utils.js |
Scribl::Utils
Chase Miller 2011
|
|
ScriblWrapLines
transforms text to fit in a column of given width
|
function ScriblWrapLines(max, text) {
var lines = [];
text = "" + text;
var temp = "";
var chcount = 0;
var linecount = 0;
var words = text.split(" ");
for (var i=0; i < words.length; i++) {
if ((words[i].length + temp.length) <= max)
temp += " " + words[i]
else {
if (temp == "") {
trunc1 = words[i].slice(0, max-1);
temp += " " + trunc1 + "-"
trunc2 = words[i].slice(max, words[i].length);
words.splice(i+1, 0, trunc2);
lines.push(temp);
temp = "";
linecount++;
}
else {
i--;
lines.push(temp);
linecount++;
temp = "";
}
}
}
linecount++;
lines.push(temp)
return ([lines, linecount]);
}
|
create unique ids
|
var idCounter = 0;
_uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
Object.keys=Object.keys||function(o,k,r){r=[];for(k in o)r.hasOwnProperty.call(o,k)&&r.push(k);return r}
|
add indexOf if not implemented for compatibility
with older browsers
|
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement
|
, fromIndex
|
) {
"use strict";
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 0) {
n = Number(arguments[1]);
if (n !== n) {
n = 0;
} else if (n !== 0 && n !== Infinity && n !== -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
}
}
|
| Scribl.svg.js |
utility functions for converting canvas to svg
|
var CanvasToSVG = {
idCounter: 0,
convert: function(sourceCanvas, targetSVG, x, y) {
var svgNS = "http:
var xlinkNS = "http:
var image = sourceCanvas.toDataURL();
var svgimg = document.createElementNS(svgNS, "image");
svgimg.setAttribute('id', 'importedCanvas_' + this.idCounter++);
svgimg.setAttributeNS(xlinkNS, 'xlink:href', image);
svgimg.setAttribute('x', x ? x : 0);
svgimg.setAttribute('y', y ? y : 0);
svgimg.setAttribute('width', sourceCanvas.width);
svgimg.setAttribute('height', sourceCanvas.height);
svgimg.imageData = sourceCanvas.toDataURL();
targetSVG.appendChild(svgimg);
}
}
var toXml = function(str) {
return $('<p/>').text(str).html();
};
var svgToString = function(svgcontent) {
while (removeUnusedDefElems() > 0) {};
pathActions.clear(true);
$.each(svgcontent.childNodes, function(i, node) {
if(i && node.nodeType == 8 && node.data.indexOf('Created with') >= 0) {
svgcontent.insertBefore(node, svgcontent.firstChild);
}
});
if(current_group) {
leaveContext();
selectOnly([current_group]);
}
var naked_svgs = [];
$(svgcontent).find('g:data(gsvg)').each(function() {
var attrs = this.attributes;
var len = attrs.length;
for(var i=0; i<len; i++) {
if(attrs[i].nodeName == 'id' || attrs[i].nodeName == 'style') {
len--;
}
}
if(len <= 0) {
var svg = this.firstChild;
naked_svgs.push(svg);
$(this).replaceWith(svg);
}
});
var output = svgToString(svgcontent, 0);
if(naked_svgs.length) {
$(naked_svgs).each(function() {
groupSvgElem(this);
});
}
return output;
}
var svgToString = function(elem, indent) {
var out = new Array();
if (elem) {
var attrs = elem.attributes,
attr,
i,
childs = elem.childNodes;
for (var i=0; i<indent; i++) out.push(" ");
out.push("<"); out.push(elem.nodeName);
if(elem.id == 'svgcontent') {
var res = getResolution();
out.push(' width="' + res.w + '" height="' + res.h + '" xmlns="'+svgns+'"');
var nsuris = {};
$(elem).find('*').andSelf().each(function() {
var el = this;
$.each(this.attributes, function(i, attr) {
var uri = attr.namespaceURI;
if(uri && !nsuris[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml' ) {
nsuris[uri] = true;
out.push(" xmlns:" + nsMap[uri] + '="' + uri +'"');
}
});
});
var i = attrs.length;
while (i--) {
attr = attrs.item(i);
var attrVal = toXml(attr.nodeValue);
if(attr.nodeName.indexOf('xmlns:') === 0) continue;
if (attrVal != "" &&
['width','height','xmlns','x','y','viewBox','id','overflow'].indexOf(attr.localName) == -1)
{
if(!attr.namespaceURI || nsMap[attr.namespaceURI]) {
out.push(' ');
out.push(attr.nodeName); out.push("=\"");
out.push(attrVal); out.push("\"");
}
}
}
} else {
for (var i=attrs.length-1; i>=0; i--) {
attr = attrs.item(i);
var attrVal = toXml(attr.nodeValue);
if (['-moz-math-font-style', '_moz-math-font-style'].indexOf(attr.localName) >= 0) continue;
if (attrVal != "") {
if(attrVal.indexOf('pointer-events') === 0) continue;
if(attr.localName === "class" && attrVal.indexOf('se_') === 0) continue;
out.push(" ");
if(attr.localName === 'd') attrVal = pathActions.convertPath(elem, true);
out.push(attr.nodeName); out.push("=\"");
out.push(attrVal); out.push("\"");
}
}
}
if (elem.hasChildNodes()) {
out.push(">");
indent++;
var bOneLine = false;
for (var i=0; i<childs.length; i++)
{
var child = childs.item(i);
switch(child.nodeType) {
case 1:
out.push("\n");
out.push(svgToString(childs.item(i), indent));
break;
case 3:
var str = child.nodeValue.replace(/^\s+|\s+$/g, "");
if (str != "") {
bOneLine = true;
out.push(toXml(str) + "");
}
break;
case 8:
out.push("\n");
out.push(new Array(indent+1).join(" "));
out.push("<!--");
out.push(child.data);
out.push("-->");
break;
}
}
indent--;
if (!bOneLine) {
out.push("\n");
for (var i=0; i<indent; i++) out.push(" ");
}
out.push("</"); out.push(elem.nodeName); out.push(">");
} else {
out.push("/>");
}
}
return out.join('');
};
|
| glyph/Scribl.arrow.js |
Scribl::Glyph::Arrow
Glyph used to draw any arrow shape
Chase Miller 2011
|
var Arrow = Glyph.extend({
|
init
Constructor, call this with new Arrow()
param: String type - a tag to associate this glyph with param: Int position - start position of the glyph param: Int length - length of the glyph param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of attributes that can be applied to glyph api: public
|
init: function(type, position, strand, opts) {
this._super(type, position, 0, strand, opts);
this.slope = 1;
this.glyphType = "Arrow";
this.thickness = 4.6
},
|
getPixelThickness
gets pixel thickness
|
getPixelThickness: function() {
var arrow = this;
var height = arrow.getHeight();
var arrowLength = /2 / Math.tan(Math.atan(arrow.slope))
return ( arrow.thickness / 10 * arrowLength );
},
|
erase
erase this glyph/feature
|
erase: function() {
var arrow = this;
var thickness = arrow.getPixelThickness();
arrow.ctx.clearRect( -thickness,0, thickness, arrow.getHeight() );
},
|
_draw
private arrow specific draw method that gets called by this.super.draw()_
- para: m
[context] - optional canvas.context
- para: m
[length] - optional length of glyph/feature
- para: m
[height] - optional height of lane
- para: m
[roundness] - optional roundness of glyph/feature
api: internal
|
_draw : function(ctx, length, height, roundness) {
var arrow = this;
var ctx = ctx || arrow.ctx;
var height = height || arrow.getHeight();
var roundness = roundness + 1 || arrow.calcRoundness();
if (roundness != undefined) roundness -= 1;
var thickness = arrow.getPixelThickness();
var arrowLength = 0;
x = y = 0;
a_b_x = x - arrowLength - roundness;
a_t_x = x - arrowLength - roundness;
a_max_x = x - arrowLength;
t = .5
a_ctrl_x = ( a_max_x - (1-t)*(1-t)*a_b_x - t*t*a_t_x ) / ( 2*(1-t)*t )
a_ctrl_y = y + height/2;
bs_slope = arrow.slope;
bs_intercept = (-a_ctrl_y) - bs_slope * a_ctrl_x;
ts_slope = -arrow.slope;
ts_intercept = (-a_ctrl_y) - ts_slope * a_ctrl_x;
a_b_y = -(bs_slope * a_b_x + bs_intercept);
a_t_y = -(ts_slope * a_t_x + ts_intercept);
ctx.beginPath();
bs_ctrl_y = y + height;
bs_ctrl_x = ( (-bs_ctrl_y - bs_intercept)/arrow.slope ); // control point
bs_slpe_x = bs_ctrl_x + roundness + roundness;
bs_slpe_y = -(bs_slope * bs_slpe_x + bs_intercept);
ctx.moveTo(bs_slpe_x, bs_slpe_y);
ctx.lineTo( a_b_x, a_b_y );
ctx.quadraticCurveTo(a_ctrl_x, a_ctrl_y, a_t_x, a_t_y);
ts_ctrl_y = y;
ts_ctrl_x = (ts_ctrl_y + ts_intercept)/arrow.slope ; // control point
ts_slpe_x = ts_ctrl_x + roundness + roundness;
ts_slpe_y = -(ts_slope * ts_slpe_x + ts_intercept);
ctx.lineTo(ts_slpe_x, ts_slpe_y);
var theta = ( Math.PI - Math.abs(Math.atan(arrow.slope)) ) - Math.PI/2;
var dX = Math.sin(theta) * thickness;
var dY = Math.cos(theta) * thickness;
var arcTX = ts_slpe_x - dX;
var arcTY = ts_slpe_y + dY;
ctx.bezierCurveTo(ts_ctrl_x, ts_ctrl_y, ts_ctrl_x-dX, ts_ctrl_y+dY, arcTX, arcTY);
ctx.lineTo(a_max_x-thickness, y + height/2);
var arcBX = bs_slpe_x - dX;
var arcBY = bs_slpe_y - dY;
ctx.lineTo(arcBX, arcBY);
ctx.bezierCurveTo(bs_ctrl_x-dX, bs_ctrl_y-dY, bs_ctrl_x, bs_ctrl_y, bs_slpe_x, bs_slpe_y);
}
});
|
| glyph/Scribl.blockarrow.js |
Scribl::Glyph::BlockArrow
Glyph used to draw any blockarrow shape
Chase Miller 2011
|
var BlockArrow = Glyph.extend({
|
init
Constructor, call this with new BlockArrow()
param: String type - a tag to associate this glyph with param: Int position - start position of the glyph param: Int length - length of the glyph param: String strand - '+' or '-' strand param: Hash [opts] - optional hash of attributes that can be applied to glyph api: public
|
init: function(type, position, length, strand, opts) {
this._super(type, position, length, strand, opts);
this.slope = 1;
this.glyphType = "BlockArrow";
},
|
_draw
private blockarrow specific draw method that gets called by this.super.draw()_
- para: m
[context] - optional canvas.context
- para: m
[length] - optional length of glyph/feature
- para: m
[height] - optional height of lane
- para: m
[roundness] - optional roundness of glyph/feature
api: internal
|
_draw : function(ctx, length, height, roundness) {
var blockarrow = this;
var ctx = ctx || blockarrow.ctx;
var length = length || blockarrow.getPixelLength();
var height = height || blockarrow.getHeight();
var roundness = roundness + 1 || blockarrow.calcRoundness();
if (roundness != undefined) roundness -= 1;
var side = length*.75;
x = y = 0;
tc_ctrl_x = x;
tc_ctrl_y = y;
tc_lgth_x = x + roundness;
tc_lgth_y = y;
tc_wdth_x = x;
tc_wdth_y = y + roundness;
bc_ctrl_x = x;
bc_ctrl_y = y + height;
bc_lgth_x = x + roundness;
bc_lgth_y = y + height;
bc_wdth_x = x;
bc_wdth_y = y + height - roundness;
a_b_x = x + length - roundness;
a_t_x = x + length - roundness;
a_max_x = x + length;
t = .5
a_ctrl_x = Math.round( (( a_max_x - (1-t)*(1-t)*a_b_x - t*t*a_t_x ) / ( 2*(1-t)*t ))*10 )/10;
a_ctrl_y = y + height/2;
bs_slope = blockarrow.slope;
bs_intercept = (-a_ctrl_y) - bs_slope * a_ctrl_x;
ts_slope = -blockarrow.slope;
ts_intercept = (-a_ctrl_y) - ts_slope * a_ctrl_x;
a_b_y = -(Math.round( (bs_slope * a_b_x + bs_intercept)*10 )/10);
a_t_y = -(Math.round( (ts_slope * a_t_x + ts_intercept)*10 )/10);
bs_ctrl_y = y + height;
bs_ctrl_x = ( (-bs_ctrl_y - bs_intercept)/blockarrow.slope ); // control point
if (bs_ctrl_x < x ) {
var r = new Rect(blockarrow.type, 0, length);
r._draw(ctx, length, height, roundness);
return;
}
bs_lgth_y = y + height;
bs_lgth_x = bs_ctrl_x - roundness;
bs_slpe_x = bs_ctrl_x + roundness;
bs_slpe_y = -(Math.round( (bs_slope * bs_slpe_x + bs_intercept)*10 )/10);
ts_ctrl_y = y;
ts_ctrl_x = (ts_ctrl_y + ts_intercept)/blockarrow.slope ; // control point
ts_lgth_y = y;
ts_lgth_x = ts_ctrl_x - roundness;
ts_slpe_x = ts_ctrl_x + roundness;
ts_slpe_y = -(Math.round( (ts_slope * ts_slpe_x + ts_intercept)*10 )/10);
ctx.beginPath();
ctx.moveTo(tc_lgth_x, tc_lgth_y);
ctx.quadraticCurveTo(tc_ctrl_x, tc_ctrl_y, tc_wdth_x, tc_wdth_y);
ctx.lineTo(bc_wdth_x, bc_wdth_y);
ctx.quadraticCurveTo(bc_ctrl_x, bc_ctrl_y, bc_lgth_x, bc_lgth_y);
ctx.lineTo(bs_lgth_x, bs_lgth_y);
ctx.quadraticCurveTo(bs_ctrl_x, bs_ctrl_y, bs_slpe_x, bs_slpe_y);
ctx.lineTo( a_b_x, a_b_y );
ctx.quadraticCurveTo(a_ctrl_x, a_ctrl_y, a_t_x, a_t_y);
ctx.lineTo(ts_slpe_x, ts_slpe_y);
ctx.quadraticCurveTo(ts_ctrl_x, ts_ctrl_y, ts_lgth_x, ts_lgth_y);
ctx.lineTo(tc_lgth_x, tc_lgth_y);
}
});
|
| glyph/Scribl.seq.js |
Scribl::Glyph::Seq
Glyph used to letters e.g nucleotides or proteins
Chase Miller 2011
|
var Seq = Glyph.extend({
|
init
Constructor, call this with new seq()
param: String type - a tag to associate this glyph with param: Int position - start position of the glyph param: Int length - length of the glyph param: Hash [opts] - optional hash of attributes that can be applied to glyph api: public
|
init: function(type, position, length, seq, opts) {
this.seq = seq;
this.insertions = [];
this.fraction = 1;
this.fractionLevel = 0.3;
this.glyphType = "Seq";
this.font = "px courier";
this.chars = {};
this.chars.width = undefined;
this.chars.height = undefined;
this.chars.list = ['A', 'G', 'T', 'C', 'N', '-'];
this._super(type, position, length, undefined, opts);
},
|
_draw
private letter specific draw method that gets called by this.super.draw()_
- para: m
[context] - optional canvas.context
- para: m
[length] - optional length of glyph/feature
- para: m
[height] - optional height of lane
api: internal
|
_draw: function(ctx, length, height) {
var seq = this;
var fraction = 1;
if (seq.lane.chart.ntsToPixels() <= seq.fractionLevel)
fraction = this.fraction
var ctx = ctx || seq.ctx;
var length = length || seq.getPixelLength();
var height = height || seq.getHeight();
var left = seq.getPixelPositionX();
var top = seq.getPixelPositionY();
var chars = SCRIBL.chars;
if ( !chars.heights[height] ) {
chars.heights[height] = [];
for (var i=0; i < this.chars.list.length; i++) {
var nt = this.chars.list[i];
var ntName = nt;
if (nt == '-') { ntName = 'dash'; }
var charName = "nt_" + ntName + '_bg';
this.createChar(nt, chars.nt_color, chars[charName], height);
}
}
x = y = 0;
if (seq.imgCanvas) {
ctx.drawImage(seq.imgCanvas, left, top - height*fraction, length, height*fraction);
} else {
ctx.save();
ctx.beginPath();
ctx.textBaseline = "middle";
var origFont = ctx.font;
var size = /[\d+px]/.exec(origFont) + 'px';
ctx.font = size + " courier";
ctx.fillStyle = 'black';
ctx.textAlign = 'left';
var seqPx = this.seq.length * chars.heights[height].width;
seq.imgCanvas = document.createElement('canvas');
seq.imgCanvas.width = seqPx;
seq.imgCanvas.height = height;
var tmpCtx = seq.imgCanvas.getContext('2d');
var pos = 0;
var k = 0;
for (var i=0; i < this.seq.length; i++) {
if (!chars.heights[height][ this.seq[i] ]) {
this.createChar(this.seq[i], 'black', 'white', height);
}
var charGlyph = this.seq[i];
if (this.insertions.length > 1) {
var h = 2;
}
if (this.insertions[k] && this.insertions[k]['pos'] != undefined) {
if (this.insertions[k]['pos'] -1 == i ){
charGlyph += 'rightInsert';
} else if (this.insertions[k] && this.insertions[k]['pos'] == i){
charGlyph += 'leftInsert';
k++;
}
}
tmpCtx.drawImage(chars.heights[height][ charGlyph ],pos,y);
pos += chars.heights[height].width;
}
ctx.drawImage(seq.imgCanvas, x, height - height*fraction, length, height*fraction);
ctx.font = origFont;
ctx.restore();
}
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(length, y);
ctx.lineTo(length, y + height);
ctx.lineTo(x, y+height);
ctx.lineTo(x, y);
ctx.fillStyle = 'rgba(0,0,0,0)';
if (seq.lane.chart.ntsToPixels() <= seq.fractionLevel)
ctx.strokeStyle = 'rgba(0,0,0,1)';
else
ctx.strokeStyle = 'rgba(0,0,0,0)';
ctx.stroke();
ctx.closePath();
},
|
_createChar
creates glyphs of a given character
param: Char - the char to create glyph of param: String - string of char color in rgb or hex param: String - string of char background color in rgb or hex param: Int - height of glyph api: internal
|
createChar: function(theChar, color, backgroundColor, height) {
var seq = this;
var chart = seq.lane.track.chart;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var buffer = 2;
var fontsize = height - buffer;
ctx.font = fontsize + 'px courier';
var width = ctx.measureText(theChar).width + buffer;
canvas.height = height;
canvas.width = width;
SCRIBL.chars.heights[height].width = width;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.font = fontsize + 'px courier';
canvas.height = height;
canvas.width = width;
var fillStyle = ctx.fillStyle;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0,0, width, height);
ctx.fillStyle = color;
ctx.textAlign = 'center';
ctx.textBaseline = "middle";
ctx.fillText(theChar, /2, height/2);
SCRIBL.chars.heights[height][theChar] = canvas;
ctx.fillStyle = fillStyle;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.font = fontsize + 'px courier';
canvas.height = height;
canvas.width = width;
var fillStyle = ctx.fillStyle;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0,0, width, height);
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.moveTo(0,height);
ctx.arcTo(width,height, width,0, height/2);
ctx.lineTo(width,height);
ctx.lineTo(0, height)
ctx.closePath();
ctx.fill();
ctx.fillStyle = color;
ctx.textAlign = 'center';
ctx.textBaseline = "middle";
ctx.fillText(theChar, /2, height/2);
SCRIBL.chars.heights[height][theChar + 'rightInsert'] = canvas;
ctx.fillStyle = fillStyle;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.font = fontsize + 'px courier';
canvas.height = height;
canvas.width = width;
var fillStyle = ctx.fillStyle;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0,0, width, height);
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.moveTo(width,height);
ctx.arcTo(0,height, 0,0, height/2);
ctx.lineTo(0,height);
ctx.lineTo(width, height)
ctx.closePath();
ctx.fill();
ctx.fillStyle = color;
ctx.textAlign = 'center';
ctx.textBaseline = "middle";
ctx.fillText(theChar, /2, height/2);
SCRIBL.chars.heights[height][theChar + 'leftInsert'] = canvas;
ctx.fillStyle = fillStyle;
}
});
|
| glyph/Scribl.complex.js |
Scribl::Glyph::Complex
Complex is used to draw any feature that has splices
(e.g gene with subFeatures and introns, etc) Or
any feature that should be made up of other features
Chase Miller 2011
|
var Complex = Glyph.extend({
|
init
Constructor, call this with new Complex()
param: String type - a tag to associate this glyph with param: Int position - start position of the glyph param: Int length - length of the glyph param: Array subFeatures - array of derived Glyph objects (e.g Rect, Arrow, etc...) param: Hash [opts] - optional hash of attributes that can be applied to glyph api: public
|
init: function(type, position, length, strand, subFeatures, opts) {
this._super(type, position, length, strand, opts);
this.slope = 1;
this.glyphType = "Complex";
this.subFeatures = subFeatures;
this.line = new Line(type, 0, length);
this.line.parent = this;
this.line.color = "black";
this.line.thickness = 2;
},
|
addSubFeature
adds subFeature to complex glyph/feature
- param: subFeature
- a derived Glyph object (e.g. Rect, Arrow, etc..)
api: public
|
addSubFeature : function(subFeature) {
this.subFeatures.push(subFeature);
},
|
_draw
private complex specific draw method that gets called by this.super.draw()_
- para: m
[context] - optional canvas.context
- para: m
[length] - optional length of glyph/feature
- para: m
[height] - optional height of lane
- para: m
[roundness] - optional roundness of glyph/feature
api: internal
|
_draw : function(ctx, length, height, roundness) {
var complex = this;
var ctx = ctx || complex.ctx;
var length = length || complex.getPixelLength();
var height = height || complex.getHeight();
var roundness = roundness + 1 || complex.calcRoundness();
if (roundness != undefined) roundness -= 1;
x = y = 0;
ctx.translate(-complex.getPixelPositionX(), 0);
complex.line.lane = this.lane;
complex.line.draw();
var numsubFeatures = complex.subFeatures.length
for (var i=0; i< numsubFeatures; i++) {
complex.subFeatures[i].parent = complex;
complex.subFeatures[i].lane = complex.lane;
complex.subFeatures[i].draw();
}
ctx.translate(complex.getPixelPositionX(), 0);
ctx.beginPath();
}
});
|
| glyph/Scribl.line.js |
Scribl::Glyph::Line
Glyph used to draw any line shape
Chase Miller 2011
|
var Line = Glyph.extend({
|
init
Constructor, call this with new Line()
param: String type - a tag to associate this glyph with param: Int position - start position of the glyph param: Int length - length of the glyph param: Hash [opts] - optional hash of attributes that can be applied to glyph api: public
|
init: function(type, position, length, opts) {
this.thickness = 2;
this._super(type, position, length, undefined, opts);
this.glyphType = "Line";
},
|
_draw
private line specific draw method that gets called by this.super.draw()_
- para: m
[context] - optional canvas.context
- para: m
[length] - optional length of glyph/feature
- para: m
[height] - optional height of lane
- para: m
[roundness] - optional roundness of glyph/feature
api: internal
|
_draw: function(ctx, length, height, roundness) {
var line = this;
var ctx = ctx || line.ctx;
var length = length || line.getPixelLength();
var height = height || line.getHeight();
x = y = 0;
ctx.beginPath();
ctx.moveTo(x, /2 - line.thickness/2);
ctx.lineTo(x, /2 + line.thickness/2);
ctx.lineTo(x+length, /2 + line.thickness/2);
ctx.lineTo(x+length, /2 - line.thickness/2);
}
});
|
| glyph/Scribl.rect.js |
Scribl::Glyph::Rect
Glyph used to draw any rectangle shape
Chase Miller 2011
|
var Rect = Glyph.extend({
|
init
Constructor, call this with new Rect()
param: String type - a tag to associate this glyph with param: Int position - start position of the glyph param: Int length - length of the glyph param: Hash [opts] - optional hash of attributes that can be applied to glyph api: public
|
init: function(type, position, length, opts) {
this._super(type, position, length, undefined, opts);
this.glyphType = "Rect";
},
|
_draw
private rect specific draw method that gets called by this.super.draw()_
- para: m
[context] - optional canvas.context
- para: m
[length] - optional length of glyph/feature
- para: m
[height] - optional height of lane
- para: m
[roundness] - optional roundness of glyph/feature
api: internal
|
_draw: function(ctx, length, height, roundness) {
var rect = this;
var ctx = ctx || rect.ctx;
var length = length || rect.getPixelLength();
var height = height || rect.getHeight();
var roundness = roundness + 1 || rect.calcRoundness();
if (roundness != undefined) roundness -= 1
x = y = 0;
ctx.beginPath();
tlc_ctrl_x = x;
tlc_ctrl_y = y;
tlc_lgth_x = x + roundness;
tlc_lgth_y = y;
tlc_wdth_x = x;
tlc_wdth_y = y + roundness;
blc_ctrl_x = x;
blc_ctrl_y = y + height;
blc_lgth_x = x + roundness;
blc_lgth_y = y + height;
blc_wdth_x = x;
blc_wdth_y = y + height - roundness;
brc_ctrl_x = x + length;
brc_ctrl_y = y + height;
brc_lgth_x = x + length - roundness;
brc_lgth_y = y + height;
brc_wdth_x = x + length;
brc_wdth_y = y + height - roundness;
trc_ctrl_x = x + length;
trc_ctrl_y = y;
trc_lgth_x = x + length - roundness;
trc_lgth_y = y;
trc_wdth_x = x + length;
trc_wdth_y = y + roundness;
ctx.moveTo(tlc_lgth_x, tlc_lgth_y);
ctx.quadraticCurveTo(tlc_ctrl_x, tlc_ctrl_y, tlc_wdth_x, tlc_wdth_y);
ctx.lineTo(blc_wdth_x, blc_wdth_y);
ctx.quadraticCurveTo(blc_ctrl_x, blc_ctrl_y, blc_lgth_x, blc_lgth_y);
ctx.lineTo(brc_lgth_x, brc_lgth_y);
ctx.quadraticCurveTo(brc_ctrl_x, brc_ctrl_y, brc_wdth_x, brc_wdth_y);
ctx.lineTo(trc_wdth_x, trc_wdth_y);
ctx.quadraticCurveTo(trc_ctrl_x, trc_ctrl_y, trc_lgth_x, trc_lgth_y);
ctx.lineTo(tlc_lgth_x, tlc_lgth_y);
}
});
|
| parsers/bed.js |
Bed parser
|
function bed(file, chart) {
var lines = file.split("\n");
var features = [];
var max = undefined;
var min = undefined;
var trackInfo = lines[0];
numFeatures = lines.length
for( var j=1; j < numFeatures; j++ ) {
if( lines[j] == "" ) break;
var fields = lines[j].split(" ");
var chromStart = parseInt(fields[1]);
var chromEnd = parseInt(fields[2]);
var name = fields[0] + ": " + fields[3];
var orientation = fields[5];
var itemRgb = fields[8];
var blockLengths = fields[10].split(',');
var blockStarts = fields[11].split(',');
var complex = chart.addFeature( new Complex('complex', chromStart, chromEnd, orientation, [], {'color':itemRgb, 'name':name}) );
for( var k=0; k<blockLengths.length; k++) {
if( blockLengths[k] == "") break;
complex.addSubFeature( new BlockArrow('complex', parseInt(blockStarts[k]), parseInt(blockLengths[k]), orientation));
}
}
}
|
| parsers/genbank.js |
Genbank parser
|
function genbank(file, bchart) {
var lines = file.split("\n");
var re = new RegExp(/\s+gene\s+([a-z]*)\(?(\d+)\.\.(\d+)/);
var genes = [];
var max = undefined;
var min = undefined;
for( var j=0; j < lines.length; j++ ) {
var gene_info;
if (gene_info = lines[j].match(re)) {
gene_info.shift();
genes.push(gene_info);
var end = gene_info[2];
if (max == undefined || max > end)
max = end;
var position = gene_info[1];
if (min == undefined || min < position)
min = position;
}
}
bchart.scale.max = max;
bchart.scale.min = min;
for(var i=0; i < genes.length; i++ ) {
var strand = '+';
if ( genes[i][0] == "complement" )
strand = '-';
var position = genes[i][1];
var end = genes[i][2];
position = position - 1 + 1;
var length = end - position;
bchart.addGene(position, length, strand);
}
}
|