1 // VIEW 2 3 /** 4 * @class 5 */ 6 function Track(name, key, loaded, changeCallback) { 7 this.name = name; 8 this.key = key; 9 this.loaded = loaded; 10 this.changed = changeCallback || function() {}; 11 this.height = 0; 12 this.shown = true; 13 this.empty = false; 14 } 15 16 Track.prototype.load = function(url) { 17 dojo.xhrGet({ url: url, 18 handleAs: "json", 19 failOk: true, 20 load: dojo.hitch( this, function(o) { this.loadSuccess(o, url); }), 21 error: dojo.hitch( this, function(o) { this.loadFail(o, url); }) 22 }); 23 }; 24 25 Track.prototype.loadSuccess = function(error) { 26 this.setLoaded(); 27 }; 28 29 Track.prototype.loadFail = function(error) { 30 if( error.status != 404 ) 31 console.error(''+error); 32 this.empty = true; 33 this.setLoaded(); 34 }; 35 36 37 Track.prototype.heightUpdate = function(height, blockIndex) { 38 if (!this.shown) { 39 this.heightUpdateCallback(0); 40 return; 41 } 42 43 if (blockIndex !== undefined) 44 this.blockHeights[blockIndex] = height; 45 46 this.height = Math.max( this.height, height ); 47 if ( ! this.inShowRange ) { 48 this.heightUpdateCallback( Math.max( this.labelHeight, this.height ) ); 49 } 50 }; 51 52 Track.prototype.setViewInfo = function(heightUpdate, numBlocks, 53 trackDiv, labelDiv, 54 widthPct, widthPx, scale) { 55 56 this.heightUpdateCallback = heightUpdate; 57 this.div = trackDiv; 58 this.label = labelDiv; 59 this.widthPct = widthPct; 60 this.widthPx = widthPx; 61 62 this.leftBlank = document.createElement("div"); 63 this.leftBlank.className = "blank-block"; 64 this.rightBlank = document.createElement("div"); 65 this.rightBlank.className = "blank-block"; 66 this.div.appendChild(this.rightBlank); 67 this.div.appendChild(this.leftBlank); 68 69 this.sizeInit(numBlocks, widthPct); 70 this.labelHTML = ""; 71 this.labelHeight = 0; 72 }; 73 74 Track.prototype.hide = function() { 75 if (this.shown) { 76 this.div.style.display = "none"; 77 this.shown = false; 78 } 79 }; 80 81 Track.prototype.show = function() { 82 if (!this.shown) { 83 this.div.style.display = "block"; 84 this.shown = true; 85 } 86 }; 87 88 Track.prototype.initBlocks = function() { 89 this.blocks = new Array(this.numBlocks); 90 this.blockHeights = new Array(this.numBlocks); 91 for (var i = 0; i < this.numBlocks; i++) this.blockHeights[i] = 0; 92 this.firstAttached = null; 93 this.lastAttached = null; 94 this._adjustBlanks(); 95 }; 96 97 Track.prototype.clear = function() { 98 if (this.blocks) { 99 for (var i = 0; i < this.numBlocks; i++) 100 this._hideBlock(i); 101 } 102 this.initBlocks(); 103 }; 104 105 Track.prototype.setLabel = function(newHTML) { 106 if (this.label === undefined || this.labelHTML == newHTML ) 107 return; 108 109 this.labelHTML = newHTML; 110 dojo.query('.track-label-text',this.label) 111 .forEach(function(n){ n.innerHTML = newHTML; }); 112 this.labelHeight = this.label.offsetHeight; 113 }; 114 115 Track.prototype.transfer = function() {}; 116 117 Track.prototype.startZoom = function(destScale, destStart, destEnd) {}; 118 Track.prototype.endZoom = function(destScale, destBlockBases) {}; 119 120 Track.prototype.showRange = function(first, last, startBase, bpPerBlock, scale, 121 containerStart, containerEnd) { 122 if (this.blocks === undefined) return 0; 123 124 // this might make more sense in setViewInfo, but the label element 125 // isn't in the DOM tree yet at that point 126 if ((this.labelHeight == 0) && this.label) 127 this.labelHeight = this.label.offsetHeight; 128 129 this.inShowRange = true; 130 this.height = this.labelHeight; 131 132 var firstAttached = (null == this.firstAttached ? last + 1 : this.firstAttached); 133 var lastAttached = (null == this.lastAttached ? first - 1 : this.lastAttached); 134 135 var i, leftBase; 136 var maxHeight = 0; 137 //fill left, including existing blocks (to get their heights) 138 for (i = lastAttached; i >= first; i--) { 139 leftBase = startBase + (bpPerBlock * (i - first)); 140 this._showBlock(i, leftBase, leftBase + bpPerBlock, scale, 141 containerStart, containerEnd); 142 } 143 //fill right 144 for (i = lastAttached + 1; i <= last; i++) { 145 leftBase = startBase + (bpPerBlock * (i - first)); 146 this._showBlock(i, leftBase, leftBase + bpPerBlock, scale, 147 containerStart, containerEnd); 148 } 149 150 //detach left blocks 151 var destBlock = this.blocks[first]; 152 for (i = firstAttached; i < first; i++) { 153 this.transfer(this.blocks[i], destBlock, scale, 154 containerStart, containerEnd); 155 this.cleanupBlock(this.blocks[i]); 156 this._hideBlock(i); 157 } 158 //detach right blocks 159 destBlock = this.blocks[last]; 160 for (i = lastAttached; i > last; i--) { 161 this.transfer(this.blocks[i], destBlock, scale, 162 containerStart, containerEnd); 163 this.cleanupBlock(this.blocks[i]); 164 this._hideBlock(i); 165 } 166 167 this.firstAttached = first; 168 this.lastAttached = last; 169 this._adjustBlanks(); 170 this.inShowRange = false; 171 this.heightUpdate(this.height); 172 return 1; 173 }; 174 175 Track.prototype.cleanupBlock = function() {}; 176 177 Track.prototype._hideBlock = function(blockIndex) { 178 if (this.blocks[blockIndex]) { 179 this.div.removeChild(this.blocks[blockIndex]); 180 this.blocks[blockIndex] = undefined; 181 this.blockHeights[blockIndex] = 0; 182 } 183 }; 184 185 Track.prototype._adjustBlanks = function() { 186 if ((this.firstAttached === null) 187 || (this.lastAttached === null)) { 188 this.leftBlank.style.left = "0px"; 189 this.leftBlank.style.width = "50%"; 190 this.rightBlank.style.left = "50%"; 191 this.rightBlank.style.width = "50%"; 192 } else { 193 this.leftBlank.style.width = (this.firstAttached * this.widthPct) + "%"; 194 this.rightBlank.style.left = ((this.lastAttached + 1) 195 * this.widthPct) + "%"; 196 this.rightBlank.style.width = ((this.numBlocks - this.lastAttached - 1) 197 * this.widthPct) + "%"; 198 } 199 }; 200 201 Track.prototype.hideAll = function() { 202 if (null == this.firstAttached) return; 203 for (var i = this.firstAttached; i <= this.lastAttached; i++) 204 this._hideBlock(i); 205 206 207 this.firstAttached = null; 208 this.lastAttached = null; 209 this._adjustBlanks(); 210 //this.div.style.backgroundColor = "#eee"; 211 }; 212 213 Track.prototype.setLoaded = function() { 214 this.loaded = true; 215 this.hideAll(); 216 this.changed(); 217 }; 218 219 Track.prototype._loadingBlock = function(blockDiv) { 220 blockDiv.appendChild(document.createTextNode("Loading...")); 221 blockDiv.style.backgroundColor = "#eee"; 222 return 50; 223 }; 224 225 Track.prototype._showBlock = function(blockIndex, startBase, endBase, scale, 226 containerStart, containerEnd) { 227 if (this.blocks[blockIndex]) { 228 this.heightUpdate(this.blockHeights[blockIndex], blockIndex); 229 return; 230 } 231 if (this.empty) { 232 this.heightUpdate(this.labelHeight, blockIndex); 233 return; 234 } 235 236 var blockDiv = document.createElement("div"); 237 blockDiv.className = "block"; 238 blockDiv.style.left = (blockIndex * this.widthPct) + "%"; 239 blockDiv.style.width = this.widthPct + "%"; 240 blockDiv.startBase = startBase; 241 blockDiv.endBase = endBase; 242 if (this.loaded) { 243 this.fillBlock(blockIndex, 244 blockDiv, 245 this.blocks[blockIndex - 1], 246 this.blocks[blockIndex + 1], 247 startBase, 248 endBase, 249 scale, 250 this.widthPx, 251 containerStart, 252 containerEnd); 253 } else { 254 this._loadingBlock(blockDiv); 255 } 256 257 this.blocks[blockIndex] = blockDiv; 258 this.div.appendChild(blockDiv); 259 }; 260 261 Track.prototype.moveBlocks = function(delta) { 262 var newBlocks = new Array(this.numBlocks); 263 var newHeights = new Array(this.numBlocks); 264 var i; 265 for (i = 0; i < this.numBlocks; i++) 266 newHeights[i] = 0; 267 268 var destBlock; 269 if ((this.lastAttached + delta < 0) 270 || (this.firstAttached + delta >= this.numBlocks)) { 271 this.firstAttached = null; 272 this.lastAttached = null; 273 } else { 274 this.firstAttached = Math.max(0, Math.min(this.numBlocks - 1, 275 this.firstAttached + delta)); 276 this.lastAttached = Math.max(0, Math.min(this.numBlocks - 1, 277 this.lastAttached + delta)); 278 if (delta < 0) 279 destBlock = this.blocks[this.firstAttached - delta]; 280 else 281 destBlock = this.blocks[this.lastAttached - delta]; 282 } 283 284 for (i = 0; i < this.blocks.length; i++) { 285 var newIndex = i + delta; 286 if ((newIndex < 0) || (newIndex >= this.numBlocks)) { 287 //We're not keeping this block around, so delete 288 //the old one. 289 if (destBlock && this.blocks[i]) 290 this.transfer(this.blocks[i], destBlock); 291 this._hideBlock(i); 292 } else { 293 //move block 294 newBlocks[newIndex] = this.blocks[i]; 295 if (newBlocks[newIndex]) 296 newBlocks[newIndex].style.left = 297 ((newIndex) * this.widthPct) + "%"; 298 299 newHeights[newIndex] = this.blockHeights[i]; 300 } 301 } 302 this.blocks = newBlocks; 303 this.blockHeights = newHeights; 304 this._adjustBlanks(); 305 }; 306 307 Track.prototype.sizeInit = function(numBlocks, widthPct, blockDelta) { 308 var i, oldLast; 309 this.numBlocks = numBlocks; 310 this.widthPct = widthPct; 311 if (blockDelta) this.moveBlocks(-blockDelta); 312 if (this.blocks && (this.blocks.length > 0)) { 313 //if we're shrinking, clear out the end blocks 314 var destBlock = this.blocks[numBlocks - 1]; 315 for (i = numBlocks; i < this.blocks.length; i++) { 316 if (destBlock && this.blocks[i]) 317 this.transfer(this.blocks[i], destBlock); 318 this._hideBlock(i); 319 } 320 oldLast = this.blocks.length; 321 this.blocks.length = numBlocks; 322 this.blockHeights.length = numBlocks; 323 //if we're expanding, set new blocks to be not there 324 for (i = oldLast; i < numBlocks; i++) { 325 this.blocks[i] = undefined; 326 this.blockHeights[i] = 0; 327 } 328 this.lastAttached = Math.min(this.lastAttached, numBlocks - 1); 329 if (this.firstAttached > this.lastAttached) { 330 //not sure if this can happen 331 this.firstAttached = null; 332 this.lastAttached = null; 333 } 334 335 if (this.blocks.length != numBlocks) throw new Error("block number mismatch: should be " + numBlocks + "; blocks.length: " + this.blocks.length); 336 for (i = 0; i < numBlocks; i++) { 337 if (this.blocks[i]) { 338 //if (!this.blocks[i].style) console.log(this.blocks); 339 this.blocks[i].style.left = (i * widthPct) + "%"; 340 this.blocks[i].style.width = widthPct + "%"; 341 } 342 } 343 } else { 344 this.initBlocks(); 345 } 346 }; 347 348 /** 349 * Called by GenomeView when the view is scrolled: communicates the 350 * new x, y, width, and height of the view. This is needed by tracks 351 * for positioning stationary things like axis labels. 352 */ 353 Track.prototype.updateStaticElements = function( /**Object*/ coords ) { 354 this.window_info = dojo.mixin( this.window_info || {}, coords ); 355 }; 356 357 358 /* 359 360 Copyright (c) 2007-2009 The Evolutionary Software Foundation 361 362 Created by Mitchell Skinner <mitch_skinner@berkeley.edu> 363 364 This package and its accompanying libraries are free software; you can 365 redistribute it and/or modify it under the terms of the LGPL (either 366 version 2.1, or at your option, any later version) or the Artistic 367 License 2.0. Refer to LICENSE for the full license text. 368 369 */ 370