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