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