1 /** @namespace Top-level namespace, vq **/
  2 vq = {};
  3 /**
  4  * @class Abstract base class for VisQuick.  Handles properties of the Visualizations and the data models. *
  5 */
  6 vq.Base = function() {
  7       this.$properties = {};
  8 };
  9 
 10 vq.Base.prototype.properties = {};
 11 vq.Base.cast = {};
 12 
 13 vq.Base.prototype.extend =  function(proto) {
 14   return this;
 15 };
 16 
 17 /** @private **/
 18 vq.Base.prototype.property = function(name, cast) {
 19   if (!this.hasOwnProperty("properties")) {
 20     this.properties = pv.extend(this.properties);
 21   }
 22   this.properties[name] = true;
 23 
 24   /*
 25    * Define the setter-getter globally, since the default behavior should be the
 26    * same for all properties, and since the Protovis inheritance chain is
 27    * independent of the JavaScript inheritance chain. For example, anchors
 28    * define a "name" property that is evaluated on derived marks, even though
 29    * those marks don't normally have a name.
 30    */
 31     /** @private **/
 32   vq.Base.prototype.propertyMethod(name, vq.Base.cast[name] = cast);
 33   return this;
 34 };
 35 
 36 
 37 /** @private Sets the value of the property <i>name</i> to <i>v</i>. */
 38 vq.Base.prototype.propertyValue = function(name, v) {
 39   var properties = this.$properties;
 40   properties[name] = v;
 41   return v;
 42 };
 43 
 44 /**
 45  * @private Defines a setter-getter for the specified property.
 46  *
 47  * <p>If a cast function has been assigned to the specified property name, the
 48  * property function is wrapped by the cast function, or, if a constant is
 49  * specified, the constant is immediately cast. Note, however, that if the
 50  * property value is null, the cast function is not invoked.
 51  *
 52  * @param {string} name the property name.
 53  * @param {function} [cast] the cast function for this property.
 54  */
 55 vq.Base.prototype.propertyMethod = function(name, cast) {
 56   if (!cast) cast = vq.Base.cast[name];
 57   this[name] = function(v) {
 58 
 59       /* If arguments are specified, set the property value. */
 60       if (arguments.length) {
 61         var type = (typeof v == "function");
 62         this.propertyValue(name, (type & 1 && cast) ? function() {
 63             var x = v.apply(this, arguments);
 64             return (x != null) ? cast(x) : null;
 65           } : (((v != null) && cast) ? cast(v) : v)).type = type;
 66         return this;
 67       }
 68 
 69       return (this.$properties[name] != null) ? (typeof this.$properties[name] == "function") & 1 ?
 70              this.$properties[name].apply(this) :
 71               this.$properties[name] : null;
 72     };
 73 };
 74 
 75 
 76 /** @class The abstract base class for the VisQuick tools.  It provides the base properties.
 77  * @extends vq.Base
 78  */
 79 vq.Vis = function() {
 80    vq.Base.call(this);
 81 };
 82 /**
 83  *
 84  *
 85  * @type number
 86  * @name vertical_padding
 87  *
 88  * @type number
 89  * @name horizontal_padding
 90  *
 91  * @type number
 92  * @name width
 93  *
 94  * @type number
 95  * @name height
 96  *
 97  * @type string/HTMLElement
 98  * @name container
 99  *
100  */
101 vq.Vis.prototype = pv.extend(vq.Base);
102 
103 vq.Vis.prototype
104     .property("vertical_padding",Number)
105     .property("horizontal_padding",Number)
106     .property("width", Number)
107     .property("height", Number)
108     .property("container",  function(c) {
109             return (typeof c == "string")
110             ? document.getElementById(c)
111             : c; // assume that c is the passed-in element
112       });
113 
114 /* vq.models.js */
115 
116 /** @namespace The namespace for data model classes. **/
117 vq.models = {};
118 
119    /**
120      * It contains a meta-tag for the included data, as well as the data in JSON format.
121      *
122      * @class The abstract base class for the data model of each VisQuick tool.  It provides the
123      * necessary functionality to read, parse, analyze, and retain the input parameters.
124      *
125      * @param data - a JSON object
126      * @param {String} data.DATATYPE - a string describing the contents of the JSON data object
127      * @param {JSON} data.CONTENTS - a JSON object containing the necessary input to create the visualization
128      */
129 
130 vq.models.VisData = function(data){
131 
132 
133         if (data.DATATYPE != null) {
134             this.DATATYPE = data.DATATYPE;
135         } else {
136             this.DATATYPE = "VisData";
137         }
138 
139         if (data.CONTENTS != null) {
140             this.CONTENTS = data.CONTENTS;
141         }
142         /**@private */
143         this._ready=false;
144     };
145 
146 /**
147  *  Returns an identifying string used to specify the <i>CONTENTS</i>.  This ensures that the data is properly parsed by a visualization
148  *  which may accept multiple JSON formats.
149  *
150  * @return {String} dataType - a string describing the contents of the JSON object. This can be used to verify that the
151  *  data is the correct format for the visualization.
152  */
153 
154 
155 vq.models.VisData.prototype.getDataType = function() {
156     return this.DATATYPE;
157 };
158 
159 /**
160  *  Returns the JSON object used to contain the data, parameters, options, behavior functions, and other information necessary
161  *  to create a visualization.
162  *
163  * @return {JSON} dataType -a JSON Object containing the necessary input to create the visualization.
164  */
165 
166 vq.models.VisData.prototype.getContents = function() {
167     return this.CONTENTS;
168 };
169 vq.models.VisData.prototype.get =  function(prop) {
170     var parts = prop.split('.');
171     var obj = this;
172     for(var i = 0; i < parts.length - 1; i++) {
173         var p = parts[i];
174         if(obj[p] === undefined) {
175             obj[p] = {};
176         }
177         obj = obj[p];
178     }
179     p=parts[parts.length -1];
180     return obj[p] === undefined ?  undefined : obj[p];
181 };
182 
183 vq.models.VisData.prototype.set = function(prop,value) {
184     var parts = prop.split('.');
185     var obj = this;
186     for(var i = 0; i < parts.length - 1; i++) {
187         var p = parts[i];
188         if(obj[p] === undefined) {
189             obj[p] = {};
190         }
191         obj = obj[p];
192     }
193     p = parts[parts.length - 1];
194     obj[p] =  value === undefined ? null : value;
195     return this;
196 };
197 
198     vq.models.VisData.prototype.isDataReady = function() {
199         return this._ready;
200     };
201 
202     vq.models.VisData.prototype.setDataReady = function(bool) {
203         this._ready = Boolean(bool);
204     };
205 
206 
207 vq.models.VisData.prototype.setValue = function(data,o) {
208     var get = vq.utils.VisUtils.get;
209     if (typeof get(data,o.id) == 'function') {
210         this.set(o.label, get(data,o.id));
211         return;
212     }
213     else {
214         if(o.cast) {
215             this.set(o.label, o.cast(get(data,o.id)));
216             return;
217         } else {
218             this.set(o.label,get(data,o.id));
219             return;
220         }
221     }
222 };
223 
224 /** private **/
225 
226 vq.models.VisData.prototype._processData = function(data) {
227     var that = this;
228     var get = vq.utils.VisUtils.get;
229 
230     if(!this.hasOwnProperty('_dataModel')) {
231         this._dataModel = pv.extend(this._dataModel);
232     }
233     data = Object(data);
234     this['_dataModel'].forEach(function(o) {
235         try{
236             if (!typeof o == 'object') { return;}
237             //use default value if nothing defined
238             if (!o.optional) {
239                 if (get(data,o.id)  === undefined) {
240                     that.set(o.label,(o.defaultValue != undefined ? o.defaultValue :  o['cast'](null)));
241                 } else { //o.id value is found and not optional
242                     that.setValue(data,o);
243                 }
244             }  else {  // it is optional
245                 if (get(data,o.id)  === undefined) {
246                     return;  //don't set it
247                 } else {
248                     that.setValue(data,o);    //set it
249                 }
250             }
251         } catch(e) {
252             console.warn('Unable to import property \"'+ o.id +'\": ' + e);
253         }
254     });
255 };
256 
257 /* vq.utils.js */
258 
259 /** @namespace The namespace for utility classes focused on visualization tools. **/
260 vq.utils = {};
261 /**
262  *
263  *
264  * Used as a static class object to reserve a useful namespace.
265  *
266  * @class Provides a set of static functions for use in creating visualizations.
267  * @namespace A set of simple functions for laying out visualizations rapidly.
268  *
269  */
270 
271 vq.utils.VisUtils =  {};
272     /**
273      * Utility function for the creation of a div with specified parameters.  Useful in structuring interface for
274      * multi-panel cooperating visualizations.
275      *
276      * @static
277      * @param {String} id -  the id of the div to be created.
278      * @param {String} [className] - the class of the created div
279      * @param {String} [innerHTML] - text to be included in the div
280      * @return divObj - a reference to the div (DOM) object
281      *
282      */
283     vq.utils.VisUtils.createDiv = function(id, className, innerHtml) {
284         var divObj;
285         try {
286             divObj = document.createElement('<div>');
287         } catch (e) {
288         }
289         if (!divObj || !divObj.name) { // Not in IE, then
290             divObj = document.createElement('div')
291         }
292         if (id) divObj.id = id;
293         if (className) {
294             divObj.className = className;
295             divObj.setAttribute('className', className);
296         }
297         if (innerHtml) divObj.innerHTML = innerHtml;
298         return divObj;
299     };
300 
301     /**
302      * Ext.ux.util.Clone Function
303      * @param {Object/Array} o Object or array to clone
304      * @return {Object/Array} Deep clone of an object or an array
305      * @author Ing. Jozef Sak�lo?
306      */
307     vq.utils.VisUtils.clone = function(o) {
308         if(!o || 'object' !== typeof o) {
309             return o;
310         }
311         var c = '[object Array]' === Object.prototype.toString.call(o) ? [] : {};
312         var p, v;
313         for(p in o) {
314             if(o.hasOwnProperty(p)) {
315                 v = o[p];
316                 if(v && 'object' === typeof v) {
317                     c[p] = vq.utils.VisUtils.clone(v);
318                 }
319                 else {
320                     c[p] = v;
321                 }
322             }
323         }
324         return c;
325     }; // eo function clone
326 
327 vq.utils.VisUtils.get =  function(obj,prop) {
328     var parts = prop.split('.');
329     for(var i = 0; i < parts.length - 1; i++) {
330         var p = parts[i];
331         if(obj[p] === undefined) {
332             obj[p] = {};
333         }
334         obj = obj[p];
335     }
336     p=parts[parts.length -1];
337     return obj[p] === undefined ?  undefined : obj[p];
338 };
339 
340 
341 
342 vq.utils.VisUtils.set = function(obj,prop,value) {
343     var parts = prop.split('.');
344     for(var i = 0; i < parts.length - 1; i++) {
345         var p = parts[i];
346         if(obj[p] === undefined) {
347             obj[p] = {};
348         }
349         obj = obj[p];
350     }
351     p = parts[parts.length - 1];
352     obj[p] = value || null;
353     return this;
354 };
355 
356 //sorting functions, etc
357 
358     vq.utils.VisUtils.alphanumeric = function(comp_a,comp_b) {	//sort order -> numbers -> letters
359         if (isNaN(comp_a || comp_b))  { // a is definitely a non-integer
360             if (isNaN( comp_b || comp_a)) {   // both are non-integers
361                 return [comp_a, comp_b].sort();   // sort the strings
362             } else {                // just a is a non-integer
363                 return 1;           // b goes first
364             }
365         } else if (isNaN(comp_b || comp_a)) {  // only b is a non-integer
366             return -1;          //a goes first
367         } else {                                    // both are integers
368             return Number(comp_a) - Number(comp_b);
369         }
370     },
371 
372 
373 //function network_node_id(node) { return node.nodeName + node.start.toFixed(4) + node.end.toFixed(4);};
374     vq.utils.VisUtils.network_node_id = function(node) {
375         var map = vq.utils.VisUtils.options_map(node);
376         if (map != null && map['label'] != undefined)
377         {return map['label'];}
378         return node.nodeName + node['start'].toFixed(2) + node['end'].toFixed(2);
379     };
380 
381 //function network_node_id(node) { return node.nodeName + node.start.toFixed(4) + node.end.toFixed(4);};
382     vq.utils.VisUtils.network_node_title = function(node) {
383         var map = vq.utils.VisUtils.options_map(node);
384         if (map != null && map['label'] != undefined)
385         {return map['label'] + ' \n' +  'Chr: ' + node.nodeName +
386                 '\nStart: ' + node['start'] +
387                 '\nEnd: ' + node['end'];}
388         return node.nodeName + ' ' +  node['start'].toFixed(2) + ' ' + node['end'].toFixed(2);
389     };
390 
391 //function tick_node_id(tick) { return tick.chr + tick.start.toFixed(4) + tick.end.toFixed(4);};
392     vq.utils.VisUtils.tick_node_id = function(tick) { return tick.value;};
393 
394     vq.utils.VisUtils.extend = function(target,source) {
395     for (var v in source) {
396           target[v] = source[v];
397     }
398         return target;
399 };
400 
401     vq.utils.VisUtils.parse_pairs = function(column,assign_str,delimit_str) {
402         var map = {}, pair_arr =[], pairs = [];
403             pair_arr =[];
404             pairs = column.split(delimit_str);
405             for (var i=0;i< pairs.length; i++) {
406                 pair_arr = pairs[i].split(assign_str);
407                 if (pair_arr.length == 2) {
408                     map[pair_arr[0]] = pair_arr[1];
409                 }
410             }
411         return map;
412     };
413 
414     vq.utils.VisUtils.options_map = function(node) {
415         var options_map = {};
416         if (node.options != null) {
417             options_map = vq.utils.VisUtils.parse_pairs(node.options,'=',',');
418         }
419         return options_map;
420     };
421 
422     vq.utils.VisUtils.wrapProperty = function(property) {
423         if (typeof property == 'function'){
424             return property;
425         } else {
426             return function() {return property;}
427         }
428     };
429 vq.utils.VisUtils.pivotArray = function(array,pivot_on,group_by,value_id,aggregate_object,
430                                         include_other_properties,filter_incomplete){
431 
432     var dims =  pv.uniq(array.map(function(c) { return c[pivot_on];})).sort();
433 
434     var nested_data = pv.nest(array)
435             .key(function(d) { return d[group_by];})
436             .map();
437 
438     var data = pv.values(nested_data).map(function(pivot_array){
439         var new_object = {};
440         if (include_other_properties) {
441             new_object = vq.utils.VisUtils.clone(pivot_array[0]);
442             delete new_object[value_id];
443             delete new_object[pivot_on];}
444         else {
445             new_object[group_by] = pivot_array[0][group_by];
446         }
447         pivot_array.forEach(  function(pivot_object) {
448             new_object[pivot_object[pivot_on]] = pivot_object[value_id];
449         });
450 
451         if (aggregate_object) {
452             switch(aggregate_object.operation) {
453                 case 'collect' :
454                     new_object[aggregate_object.column] = pv.map(pivot_array, function(data) { return data[aggregate_object.column];});
455                     break;
456                 case 'mean':
457                     new_object[aggregate_object.column] = pv.mean(pivot_array, function(data) { return data[aggregate_object.column];});
458                     break;
459                 case 'min':
460                     new_object[aggregate_object.column] = pv.min(pivot_array, function(data) { return data[aggregate_object.column];});
461                     break;
462                 case 'max':
463                     new_object[aggregate_object.column] = pv.max(pivot_array, function(data) { return data[aggregate_object.column];});
464                     break;
465                 case 'sum':
466                 default:
467                     new_object[aggregate_object.column] = pv.sum(pivot_array, function(data) { return data[aggregate_object.column];});
468             }
469         }
470         return new_object;
471         //filter out any data points which are missing a year or more
472     });
473     if(filter_incomplete) data = data.filter(function(d) { return dims.every(function(dim) { return d[dim];} );});
474     return data;
475 
476 };
477 
478 vq.utils.VisUtils.layoutChrTiles = function(tiles,overlap, max_level, treat_as_points) {
479     var points = treat_as_points || Boolean(false);
480     var new_tiles = [], chr_arr = [];
481     chr_arr = pv.uniq(tiles, function(tile) { return tile.chr;});
482     chr_arr.forEach(function(chr) {
483         new_tiles = pv.blend([new_tiles,
484                 vq.utils.VisUtils.layoutTiles(tiles.filter(function(tile) { return tile.chr == chr;}),overlap,max_level,points)]);
485     });
486     tiles.forEach(function(tile) {
487         var match = null,
488         index= 0,
489         props = pv.keys(tile);
490         do {
491              match = props.every(function(prop) { return ((tile[prop] == new_tiles[index][prop]) ||
492                         (isNaN(tile[prop] && isNaN(new_tiles[index][prop])))) ? 1 : 0;});
493             index++;
494         }
495       while (index < new_tiles.length && match != 1);
496         tile.level = new_tiles[index-1].level;
497     });
498     return tiles;
499 };
500 
501 vq.utils.VisUtils.layoutChrTicks = function(tiles,overlap,max_level) {
502     return vq.utils.VisUtils.layoutChrTiles(tiles,overlap,max_level,true);
503 };
504 
505 //tiles : {Array} of tiles.  tile is composed of start,end
506 // this returns an array with tile appended with a 'level' property representing a linear layout
507 // of non-overlapping Tiles
508 
509 vq.utils.VisUtils.layoutTiles = function(tiles,overlap,max_level, treat_as_points){
510     var points = treat_as_points || Boolean(false);
511     tiles.forEach (function(b) { b.tile_length = (b.end - b.start);});  // generate a tile length property
512     tiles = tiles.sort(function(a,b) { return (a.tile_length < b.tile_length) ? -1 :
513             (a.tile_length > b.tile_length) ? 1 : a.start < b.start ? -1 : 1 ;}).reverse();         //sort all tiles by tile length
514     if (tiles.length) {tiles[0].level = 0;}
515     tiles.forEach(function(tile,index,array) {
516 
517         var levels = array.slice(0,index)
518                 .map(
519                 function(a){
520                     var t1 = vq.utils.VisUtils.extend({},a);
521                     var t2 = vq.utils.VisUtils.extend({},tile);
522                     if(a.end == null)  t1.end = t2.start + 0.1;
523                     else if(tile.end == null) t2.end = t2.start + 0.1;
524                     return vq.utils.VisUtils._isOverlapping(t1,t2,overlap || 0, points) ? a.level : null;
525                 }
526                 );
527         levels = levels.filter(function(a) { return a != null;}).sort(pv.naturalOrder);
528         var find = 0, l_index =0;
529         while (find >= levels[l_index]) {
530             if (find == levels[l_index]) { find++;}
531             l_index++;
532         }
533         if (max_level === undefined) { tile.level = find;}
534         else
535         {tile.level  = find <= max_level ? find : Math.floor(Math.random() * (max_level + 1));}
536     });
537     return tiles;
538 };
539 
540 vq.utils.VisUtils._isOverlapping = function(tile1,tile2,overlap, treat_as_points) {
541     var point = treat_as_points || Boolean(false);
542     if (point) return ((tile1.start-overlap) <= tile2.start && (tile1.start + overlap) >= tile2.start);
543     else
544     return ((tile1.start-overlap) <= tile2.end && (tile1.end + overlap) >= tile2.start);
545 };
546 
547 //taken from PrototypeJS
548 
549 
550   vq.utils.VisUtils.cumulativeOffset = function (element) {
551     var valueT = 0, valueL = 0;
552     if (element.parentNode) {
553       do {
554         valueT += element.offsetTop  || 0;
555         valueL += element.offsetLeft || 0;
556         element = element.offsetParent;
557       } while (element);
558     }
559     return {left : valueL, top: valueT};
560   };
561 
562  vq.utils.VisUtils.viewportOffset = function(forElement) {
563     var valueT = 0, valueL = 0, docBody = document.body;
564 
565     var element = forElement;
566     do {
567       valueT += element.offsetTop  || 0;
568       valueL += element.offsetLeft || 0;
569       if (element.offsetParent == docBody &&
570         element.style.position == 'absolute') break;
571     } while (element = element.offsetParent);
572 
573     element = forElement;
574     do {
575       if (element != docBody) {
576         valueT -= element.scrollTop  || 0;
577         valueL -= element.scrollLeft || 0;
578       }
579     } while (element = element.parentNode);
580     return {left:valueL, top:valueT};
581   };
582 
583 vq.utils.VisUtils.scrollOffset = function (element) {
584     var valueT = 0, valueL = 0;
585       do {
586         valueT += element.scrollTop  || 0;
587   	    valueL += element.scrollLeft || 0;
588       } while (element = element.parentNode);
589     return {left : valueL, top: valueT};
590   };
591 
592 vq.utils.VisUtils.outerHTML = function(node){
593         // if IE, Chrome take the internal method otherwise build one
594         return node.outerHTML || (
595                                  function(n){
596                                      var div = document.createElement('div'), h;
597                                      div.appendChild( n.cloneNode(true) );
598                                      h = div.innerHTML;
599                                      div = null;
600                                      return h;
601                                  })(node);
602 };
603 
604 vq.utils.VisUtils.translateToReferenceCoord = function(coord,panel) {
605     var offset = vq.utils.VisUtils.scrollOffset(panel.root.canvas());
606     return {x:coord.x + offset.left,y:coord.y+offset.top};
607 };
608 
609 /* found on stackoverflow.com
610     credit to "broofa"
611  */
612 
613 vq.utils.VisUtils.guid = function() {
614    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
615     var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
616     return v.toString(16);
617     });
618 };
619 
620 vq.utils.VisUtils.openBrowserTab= function(url) {
621         var new_window = window.open(url,'_blank');
622         new_window.focus();
623 } ;
624 
625 vq.utils.VisUtils.disabler = function(e) {
626     if(e.preventDefault) { e.preventDefault();}
627     return false;
628 };
629 
630 vq.utils.VisUtils.disableSelect= function(el) {
631        if(el.attachEvent){
632         el.attachEvent("onselectstart",vq.utils.VisUtils.disabler);
633     } else {
634         el.addEventListener("selectstart",vq.utils.VisUtils.disabler,false);
635     }
636 };
637 
638 vq.utils.VisUtils.enableSelect = function(el){
639     if(el.attachEvent){
640         el.detachEvent("onselectstart",vq.utils.VisUtils.disabler);
641     } else {
642         el.removeEventListener("selectstart",vq.utils.VisUtils.disabler,false);
643     }
644 };
645 
646 vq.utils.VisUtils.insertGCFCode = function() {
647 
648     document.write(' \
649 <!--[if lt IE 9]> \
650     <script type="text/javascript" \
651      src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script> \
652     <style> \
653      .chromeFrameInstallDefaultStyle { \
654        width: 100%;  \
655        border: 5px solid blue; \
656      } \
657     </style> \
658             <div id="notice"></div> \
659             <div id="prompt"></div> \
660     <script> \
661           function displayGCFText() { \
662             document.getElementById("notice").innerHTML = "Internet Explorer has been detected." + \
663             "Please install the Google Chrome Frame if it is not already installed.  This will enable" + \
664             "HTML5 features necessary for the web application.<p>"+ \
665             "If the install panel does not appear, please enable Compatibility mode in your browser and reload this page."; \
666             }; \
667      window.attachEvent("onload", function() { \
668        CFInstall.check({ \
669          mode: "inline",  \
670          node: "prompt" \
671        }); \
672      }); \
673     </script> \
674   <![endif]-->');
675 };
676 
677 /**
678  * @class Provides a set of static functions for use in converting
679  * a google.visualization.DataTable object into a Protovis consumable
680  * JSON array.
681  *
682  * Intended to be used as a static class object to reserve a useful namespace.
683  *
684  * For the Circvis project, the fundamental data element is <b>node</b> JSON object consisting of:
685  *      {chromosome, start, end, value, options}
686  *          {string} chromosome
687  *          {integer} start
688  *          {integer} end
689  *          {string} value
690  *          {string} options
691  *
692  *
693  *
694  */
695 
696 vq.utils.GoogleDSUtils = {};
697 
698     /**     Converts any DataTable object into an array of JSON objects, each object consisting of a single row in the
699      *      DataTable.  The property label is obtained from the getColumnLabel() function of the google.visualiztion.DataTable class.
700      *
701      *      Column types listed as a 'number' are passed in as numeric data.  All other data types are passed in as strings.
702      *
703      *      The returned JSON array conforms to the common input format of Protovis visualizations.
704      *
705      * @param googleDataTable - google.visualizations.DataTable object returned by a google datasource query
706      * @return data_array - JSON array.
707      */
708 
709 
710     vq.utils.GoogleDSUtils.dataTableToArray = function(googleDataTable) {
711         var table = googleDataTable,
712         data_array=[],
713         headers_array=[],
714         column_type=[];
715         if (table == null) { return [];}
716         for (col=0; col<table.getNumberOfColumns(); col++){
717             headers_array.push(table.getColumnLabel(col));
718             column_type.push(table.getColumnType(col));
719         }
720 
721 
722         for (row=0; row<table.getNumberOfRows(); row++){
723             var temp_hash={};
724             for (col=0; col<table.getNumberOfColumns(); col++){
725                 if(column_type[col].toLowerCase() == 'number') {
726                     temp_hash[headers_array[col]]=table.getValue(row,col);
727                 } else {
728                     temp_hash[headers_array[col]]=table.getFormattedValue(row,col);
729                 }
730             }
731             data_array.push(temp_hash);
732         }
733         return data_array;
734     };
735 
736     /**
737      *  Converts a special DataTable object into a network object used by CircVis.
738      *  For a DataTable with fields: chr1, start1, end1, value1, options1, chr2, start2, end2, value2, options2, linkValue
739      *  the function returns an array of JSON objects consisting of two <b>node</b> JSON objects and a <b>linkValue</b>:
740      *  {node1,node2,linkValue}
741      *
742      *  The JSON array can then be passed into the NETWORK.DATA.data_array parameter used to configure Circvis.
743      *
744      * @param googleDataTable - google.visualizations.DataTable object returned by a google datasource query
745      * @returns network_json_array - a JSON array representation of a Google Visualizations DataTable object. The column label is assigned as the property label
746      */
747 
748     vq.utils.GoogleDSUtils.dataTableToNetworkArray = function(googleDataTable) {
749         var data_array = this.dataTableToArray(googleDataTable);
750         return data_array.map(function(c) { return {node1 : {chr:c['chr1'],start:c['start1'],end:c['end1'],value:c['value1'],options:c['options1']},
751         node2 : {chr:c['chr2'],start:c['start2'],end:c['end2'],value:c['value2'],options:c['options2']}, linkValue:c['linkValue']};});
752     };
753 
754     /** @private */
755     vq.utils.GoogleDSUtils.getColIndexByLabel = function(table,label) {
756         for (col = 0; col < table.getNumberOfColumns(); col++) {
757             if (label.toLowerCase() == table.getColumnLabel(col).toLowerCase()) {
758                 return col;
759             }
760         }
761         return -1;
762     };
763 
764 
765 /**
766  * @class Constructs a utility object for use with multiple-source Ajax requests.
767  * If data must be retrieved from several sources before a workflow may be started, this tool can be used to
768  * check that all necessary data is available.
769  *
770  * @param {integer} timeout number of milliseconds between checks for valid data.  Defaults to 200ms.
771  * @param {total_checks}  total number of checks to perform. Defaults to 20.
772  * @param {callback}    function to call if all data is successfully found
773  * @param {args}    an object containing the variables which will be assigned values by the Ajax responses.
774  * @param {args}    function called if timeout reached without check object being filled.
775  */
776 
777 vq.utils.SyncDatasources = function(timeout,total_checks,success_callback,args,fail_callback){
778 
779         if (timeout && !isNaN(timeout)) {
780             this.timeout = timeout;
781         } else {
782             this.timeout = 200;
783         }
784         if (total_checks && !isNaN(total_checks)) {
785             this.num_checks_until_quit = total_checks;
786         } else {
787             this.num_checks_until_quit = 20;
788         }
789         if (args instanceof Object) {
790             this.args = args;
791         } else {
792             console.log('Error: variable array not passed to timer initialize method.');
793             return;
794         }
795         if (success_callback instanceof Function) {
796             this.success_callback = success_callback
797         } else {
798             console.log('Error: callback function not passed to timer initialize method.');
799             return;
800         }
801      if (fail_callback instanceof Function) {
802             this.fail_callback = fail_callback
803         }
804         this.num_checks_so_far = 0;
805     };
806 
807     /**
808      * Initiates the data object poll.  After the maximum number of checks, a log is filed on the console and the object
809      *  aborts the polling operation.
810      */
811 
812     vq.utils.SyncDatasources.prototype.start_poll = function() {
813         var that = this;
814         setTimeout(function() { that.poll_args();},that.timeout);
815     };
816 
817     /** @private */
818     vq.utils.SyncDatasources.prototype.check_args = function(){
819         var check = true;
820         for (arg in this.args) {
821             if (this.args[arg] == null) { check = false;}
822         }
823         return check;
824     };
825 
826     /** @private */
827     vq.utils.SyncDatasources.prototype.poll_args = function(){
828         var that=this;
829         if (this.check_args()) { this.success_callback.apply(); return false;}
830         this.num_checks_so_far++;
831         if(this.num_checks_so_far >= this.num_checks_until_quit) {
832             console.log('Maximum number of polling events reached.  Datasets not loaded.  Aborting.');
833             if (this.fail_callback === undefined) { return false;}
834             else {this.fail_callback.apply(); return false;}
835         }
836         setTimeout(function() { that.poll_args();},that.timeout);
837     };
838 
839 
840 /* vq.events.js */
841 
842 /** @namespace The namespace for event classes. **/
843 vq.events = {};
844 
845 vq.events.Event = function(label,source,obj) {
846 
847     this.id = label || '';
848     this.source =source || null;
849     this.obj = obj || {} ;
850 };
851 
852 vq.events.Event.prototype.dispatch = function() {
853     vq.events.Dispatcher.dispatch(this);
854 };
855 
856 /*
857 Dispatcher
858     Manages Listeners
859             Two types of listeners:
860                     Global - event of type X from any source
861                     Distinct - event of type X from source Z
862 
863                     addListener and removeListener are overloaded to accept both kinds
864                     dispatchEvent accepts an vq.events.Event object
865                             Event object is Global if source == null
866                             Event object is Distinct if source typeof == 'string'
867  */
868 
869 vq.events.Dispatcher = (function() {
870     //private methods and variables
871         var eventList = {};
872     return {
873           addListener : function(label) {
874             var id,handler;
875             if (label === undefined || arguments.length < 2) { return; }
876             if(arguments.length == 2 && typeof arguments[1] == 'function') {
877                 handler = arguments[1];
878                 id = null;
879             } else if (arguments.length == 3 && typeof arguments[1] == 'string') {
880                 id = arguments[1], handler = arguments[2];
881             } else { return; }
882                 if (eventList[label] === undefined) {
883                     eventList[label] = {};
884                 }
885                 if (eventList[label][id] === undefined) {
886                     eventList[label][id] = [];
887                 }
888                  eventList[label][id].push(handler);
889         },
890 
891         removeListener : function(label) {
892             var id,handler;
893             if (label === undefined || arguments.length < 2) { return; }
894             if(arguments.length == 2 && typeof arguments[1] == 'function') {
895                 handler = arguments[1];
896                 id = null;
897             } else if (arguments.length == 3 && typeof arguments[1] == 'string') {
898                 id = arguments[1], handler = arguments[2];
899             }  else { return; }
900             if (eventList[label] === undefined || eventList[label][id] === undefined) {
901                 return;
902             }
903             eventList[label][id].forEach(function(e,index) {
904                 if (e === handler) {
905                     eventList[label][id].splice(index,1);
906                 }
907             });
908         },
909 
910         dispatch : function(event) {
911             var source = event.source || null;
912   	    var event_id = event.id || null;
913             var i = null,f = null;
914             if (event_id == null || eventList[event_id] === undefined) { return;}
915             if (eventList[event_id][source] !== undefined) {
916                 i =eventList[event_id][source].length;
917                 while (i--) {
918                      f =  eventList[event_id][source][i];
919                    f.call(event,event.obj);
920                 }
921             }
922             if (eventList[event_id][null] !== undefined) {
923                  i =eventList[event_id][null].length;
924                 while (i--) {
925                     f =  eventList[event_id][null][i];
926                    f.call(event,event.obj);
927                 }
928             }
929         }
930 };
931 
932 })();
933 
934 /* vq.hovercard.js */
935 
936 
937 /*
938  * @class creates a Hovercard with options to persist the display and offer multiple actions, data, tools
939  *
940  * <pre>
941  *     {
942  *     timeout : {Number} - Milliseconds to persist the display after cursor has left the event source.  If self_hover is true, the
943  *          hovercard also cancels the timer.
944  *     target_mark : {HTMLElement} - Event source that represents the protovis Mark as an SVG/HTML Element
945  *     data_config : {Object} - designates the display for the data section.  Each Object in the config consists of
946  *              {title : property}
947  *                      title : {String} - Title of the property to be displayed
948  *                       property : {String/Function} - The string value of the property to be displayed or a function that returns a string.
949  *                                                                  The function is passed the target's data as a parameter.
950  *
951  *      self_hover : {Boolean} - If true, placing the mouse cursor over the hovercard cancels the timer which hides the hovercard
952  *      include_header : {Boolean} - If true, the header of the data panel is displayed.
953 *       include_footer : {Boolean} - If true, the footer containing the CLOSE [X} action is displayed at the bottom of the hovercard
954 *       include_frame : {Boolean} - If true, the frame that contains the hovercard actions (move,pin, etc) is displayed.
955  *     </pre>
956  */
957 
958 vq.Hovercard = function(options) {
959     this.id = 'hovercard_'+vq.utils.VisUtils.guid();
960     this.hovercard = vq.utils.VisUtils.createDiv(this.id);
961     this.hovercard.style.display = 'hidden';
962     this.hovercard.style.zIndex=100000;
963     this.hovercard.style.position='absolute';
964     this.lock_display = false;
965     if (options) {
966         this.timeout = options.timeout || 800;
967         this.target_mark = options.target || null;
968         this.data_config = options.data_config || null;
969         this.tool_config = options.tool_config ||  null;
970         this.self_hover = options.self_hover || true;
971         this.include_footer = options.include_footer != null ? options.include_footer : this.self_hover || false;
972         this.include_header = options.include_header != null ? options.include_header :  this.self_hover || true;
973         this.include_frame = options.include_frame != null ? options.include_frame :  false;
974         this.transform = options.transform || new pv.Transform();
975     }
976     var that = this;
977     vq.events.Dispatcher.addListener('zoom_select','tooltip',function() { that.togglePin();});
978 };
979 
980 vq.Hovercard.prototype.show = function(anchorTarget,dataObject) {
981     var that = this;
982     if (!anchorTarget) { throw 'vq.Hovercard.show: target div not found.'; return;}
983     this.target =  anchorTarget;
984         this.hovercard.appendChild(this.renderCard(dataObject));
985     if (this.tool_config) {
986         var hr = document.createElement('div');
987         hr.setAttribute('style',"height:1px;background:#000;border:1px solid #333");
988         this.hovercard.appendChild(hr);
989         this.hovercard.appendChild(this.renderTools(dataObject));
990     }
991     if (this.include_footer) this.hovercard.appendChild(this.renderFooter());
992     this.hovercard.style.display = 'none';
993     this.hovercard.style.backgroundColor = 'white';
994     this.hovercard.style.borderWidth = '2px';
995     this.hovercard.style.borderColor = '#222';
996     this.hovercard.style.borderStyle = 'solid';
997     this.hovercard.style.font = "9px sans-serif";
998     this.hovercard.style.borderRadius = "10px";
999 
1000     this.placeInDocument();
1001 
1002     this.getContainer().className ="temp";
1003     this.start = function() {that.startOutTimer();};
1004     this.cancel = function() {
1005         that.target_mark.removeEventListener('mouseout',that.start,false);
1006         that.cancelOutTimer();
1007     };
1008     this.close = function() {that.destroy();};
1009     this.target_mark.addEventListener('mouseout',that.start, false);
1010     this.getContainer().addEventListener('mouseover',that.cancel, false);
1011     this.getContainer().addEventListener('mouseout',that.start,false);
1012 
1013 };
1014 
1015   vq.Hovercard.prototype.startOutTimer =   function() {
1016       var that = this;
1017         if (!this.outtimer_id){ this.outtimer_id = window.setTimeout(function(){that.trigger();},that.timeout); }
1018  };
1019 
1020  vq.Hovercard.prototype.cancelOutTimer =  function() {
1021         if (this.outtimer_id){
1022             window.clearTimeout(this.outtimer_id);
1023             this.outtimer_id = null;
1024         }
1025  };
1026 
1027  vq.Hovercard.prototype.trigger = function (){
1028         if(this.outtimer_id) {
1029             window.clearTimeout(this.outtimer_id);
1030             this.outtimer_id = null;
1031             this.destroy();
1032         }
1033         return false;
1034 };
1035 
1036 vq.Hovercard.prototype.togglePin = function() {
1037   this.lock_display = !this.lock_display || false;
1038     var that = this;
1039             if (this.getContainer().className=="") {
1040                 this.getContainer().addEventListener('mouseout',that.start,false);
1041                 this.getContainer().className ="temp";
1042             } else {
1043                 this.target_mark.removeEventListener('mouseout',that.start,false);
1044                 this.getContainer().removeEventListener('mouseout',that.start,false);
1045                 this.cancelOutTimer();
1046                 this.getContainer().className ="";
1047             }
1048     this.pin_div.innerHTML = this.lock_display ? vq.Hovercard.icon.pin_in : vq.Hovercard.icon.pin_out;
1049 };
1050 
1051 vq.Hovercard.prototype.placeInDocument = function(){
1052     var card = this.hovercard;
1053     var target = this.target;
1054     var offset = vq.utils.VisUtils.cumulativeOffset(this.target);
1055     offset.height = target.offsetHeight;
1056     offset.width = target.offsetWidth;
1057     card.style.display='block';
1058     card.style.visibility='hidden';
1059     card.style.top = 0 +'px';
1060     card.style.left = 0 + 'px';
1061     document.body.appendChild(card);
1062      card.style.top = offset.top + offset.height + (20 * this.transform.invert().k ) + 'px';
1063      card.style.left = offset.left + offset.width + (20 * this.transform.invert().k  ) + 'px';
1064     card.style.visibility='visible';
1065 
1066     if (this.include_frame) {
1067         var hr = document.createElement('div');
1068         hr.setAttribute('style',"height:1px;background:#000;border:1px solid #333");
1069          this.hovercard.insertBefore(hr,this.hovercard.childNodes.item(0));
1070         this.frame = this.hovercard.insertBefore(this.renderFrame(),this.hovercard.childNodes.item(0));
1071 
1072         this.attachMoveListener();}
1073 };
1074 
1075 vq.Hovercard.prototype.hide = function() {
1076     if(!this.self_hover || !this.over_self) {
1077     this.hovercard.style.display = 'none';
1078     this.hovercard.style.visibility='hidden';
1079     }
1080 };
1081 
1082 vq.Hovercard.prototype.destroy = function() {
1083     this.hide();
1084     this.target_mark.removeEventListener('mouseout',this.start, false);
1085     this.getContainer().removeEventListener('mouseout',this.start,false);
1086     this.cancelOutTimer();
1087     if (this.getContainer().parentNode == document.body) {
1088         document.body.removeChild(this.getContainer());
1089     }
1090 };
1091 
1092 vq.Hovercard.prototype.isHidden = function() {
1093     return this.hovercard.style.display == 'none' || this.hovercard.style.visibility=='hidden';
1094 };
1095 
1096 vq.Hovercard.prototype.renderCard = function(dataObject) {
1097           return  this.renderData(dataObject);
1098 };
1099 
1100 vq.Hovercard.prototype.attachMoveListener = function() {
1101     var that = this;
1102     var pos= {}, offset = {};
1103 
1104     function activateDrag(evt) {
1105          var ev = !evt?window.event:evt;
1106         //don't listen for mouseout if its a temp card
1107         if (that.getContainer().className=="temp") {
1108                 that.getContainer().removeEventListener('mouseout',that.start,false);
1109         }
1110         //begin tracking mouse movement!
1111         window.addEventListener('mousemove',trackMouse,false);
1112         offset = vq.utils.VisUtils.cumulativeOffset(that.hovercard);
1113         pos.top = ev.clientY ? ev.clientY : ev.pageY;
1114         pos.left = ev.clientX ? ev.clientX : ev.pageX;
1115         //don't allow text selection during drag
1116         window.addEventListener('selectstart',vq.utils.VisUtils.disabler,false);
1117     }
1118     function disableDrag() {
1119         //stop tracking mouse movement!
1120         window.removeEventListener('mousemove',trackMouse,false);
1121         //enable text selection after drag
1122         window.removeEventListener('selectstart',vq.utils.VisUtils.disabler,false);
1123         //start listening again for mouseout if its a temp card
1124         if (that.getContainer().className=="temp") {
1125             that.getContainer().addEventListener('mouseout',that.start,false);
1126         }
1127         pos = {};
1128     }
1129     function trackMouse(evt) {
1130         var ev = !evt?window.event:evt;
1131         var x = ev.clientX ? ev.clientX : ev.pageX;
1132         var y = ev.clientY ? ev.clientY : ev.pageY;
1133         that.hovercard.style.left = offset.left + (x - pos.left) + 'px';
1134         that.hovercard.style.top = offset.top +  (y - pos.top) + 'px';
1135     }
1136     //track mouse button for begin/end of drag
1137     this.move_div.addEventListener('mousedown',activateDrag,false);
1138     this.move_div.addEventListener('mouseup' , disableDrag,false);
1139     //track mouse button in window, too.
1140     window.addEventListener('mouseup' , disableDrag,false);
1141 };
1142 
1143 
1144 vq.Hovercard.prototype.renderFrame = function(pin_out) {
1145     var that = this;
1146     var frame = vq.utils.VisUtils.createDiv();
1147     frame.setAttribute('style','width:100%;cursor:arrow;background:#55eeff');
1148     var table = document.createElement('table');
1149     var tBody = document.createElement("tbody");
1150     table.appendChild(tBody);
1151     var trow = tBody.insertRow(-1);
1152     var tcell= trow.insertCell(-1);
1153     this.move_div = vq.utils.VisUtils.createDiv('hovercard_move');
1154     this.move_div.setAttribute('style','width:30px;height:15px;background:black;cursor:move;');
1155     this.move_div.setAttribute('title','Drag to move');
1156     vq.utils.VisUtils.disableSelect(this.move_div);
1157     tcell.appendChild(this.move_div);
1158     tcell=trow.insertCell(-1);
1159     this.pin_div = vq.utils.VisUtils.createDiv();
1160     tcell.appendChild(this.pin_div);
1161     function pin_toggle() {
1162         that.togglePin();
1163         return false;
1164     }
1165     this.pin_div.addEventListener('click', pin_toggle, false);
1166     this.pin_div.setAttribute('style', "width:15px;text-align:center;cursor:pointer;background:#FFF");
1167     vq.utils.VisUtils.disableSelect(this.pin_div);
1168     this.pin_div.innerHTML = vq.Hovercard.icon.pin_out;
1169     tcell=trow.insertCell(-1);
1170     var zoom_div = vq.utils.VisUtils.createDiv('hovercard_zoom');
1171     tcell.appendChild(zoom_div);
1172     function zoom() {
1173         vq.events.Dispatcher.dispatch(new vq.events.Event('zoom','tooltip',{hovercard:that}));
1174         return false;
1175     }
1176     zoom_div.addEventListener('click', zoom, false);
1177     tcell=trow.insertCell(-1);
1178     var select_div = vq.utils.VisUtils.createDiv('hovercard_select');
1179     tcell.appendChild(select_div);
1180     function select() {
1181         vq.events.Dispatcher.dispatch(new vq.events.Event('select','tooltip',{hovercard:that}));
1182         return false;
1183     }
1184     select_div.addEventListener('click', select, false);
1185     frame.appendChild(table);
1186     return frame;
1187 };
1188 
1189 vq.Hovercard.prototype.renderTools = function(dataObject) {
1190     var get = vq.utils.VisUtils.get;
1191     var table = document.createElement('table');
1192     table.setAttribute('style',"font-size:10px");
1193     var tBody = document.createElement("tbody");
1194     table.appendChild(tBody);
1195 
1196     if (this.tool_config) {
1197         for (var key in this.tool_config) {
1198             try {
1199                 if (!this.tool_config.hasOwnProperty(key)) continue;
1200                 var trow = tBody.insertRow(-1);
1201                 var tcell= trow.insertCell(-1);
1202                 var link = document.createElement('a');
1203                 if (typeof  this.tool_config[key] == 'function') {
1204                     link.setAttribute('href',this.tool_config[key](dataObject));
1205                 }else {
1206                     link.setAttribute('href', get(dataObject,this.tool_config[key]));
1207                 }
1208                 link.setAttribute('target',"_blank");
1209                 link.innerHTML = key;
1210                 tcell.appendChild(link);
1211             } catch(e) {
1212                 console.warn('Data not found for tools in tooltip. ' + e);
1213             }
1214         }
1215     }
1216     return table;
1217 };
1218 
1219 vq.Hovercard.icon = {};
1220 vq.Hovercard.icon.pin_in =  '<span style="font-size:15px;color:#000;" title="Click to unpin card from the window">O</span>';
1221 vq.Hovercard.icon.pin_out =  '<span style="font-size:15px;color:#000" title="Click to pin card to the window">T</span>';
1222 
1223 vq.Hovercard.prototype.renderData = function(dataObject) {
1224     var html = '';
1225     var get = vq.utils.VisUtils.get;
1226     var table = document.createElement('table');
1227     if (typeof dataObject == 'object') {
1228         if (this.include_header) {
1229             var thead = table.createTHead();
1230             var thead_row = thead.insertRow(-1);
1231             var thead_cell = thead_row.insertCell(-1);
1232             thead_cell.innerHTML = 'Property';
1233             thead_cell = thead_row.insertCell(-1);
1234             thead_cell.innerHTML = 'Value';
1235         }
1236         var tBody = document.createElement("tbody");
1237         table.appendChild(tBody);
1238 
1239         if (this.data_config) {
1240             for (var key in this.data_config) {
1241                 try {
1242                     if (!this.data_config.hasOwnProperty(key)) continue;
1243                     var trow = tBody.insertRow(-1);
1244                     var tcell= trow.insertCell(-1);
1245                     tcell.innerHTML = '<b>' + key + '</b>:';
1246                     tcell.style.textAlign = 'right';
1247                     tcell= trow.insertCell(-1);
1248                     if (typeof  this.data_config[key] == 'function') {
1249                         tcell.innerHTML= '<span>' +  this.data_config[key](dataObject) + '</span>';
1250                     }else {
1251                         tcell.innerHTML= '<span>' +  get(dataObject,this.data_config[key]) + '</span>';
1252                     }
1253                 } catch(e) {
1254                     console.warn('Data not found for tool tip: ' + e);
1255                 }
1256 
1257             }
1258         } else {
1259             pv.keys(dataObject).forEach(function(key) {
1260                 try {
1261                     var trow = tBody.insertRow(-1);
1262                     var tcell= trow.insertCell(-1);
1263                     tcell.innerHTML = '<b>' + key + '</b>:';
1264                     tcell = trow.insertCell(-1);
1265                     tcell.innerHTML = '<span>' + get(dataObject,key) + '</span>';
1266                 } catch (e) {
1267                     console.warn('Data not found for tool tip: ' + e);
1268                 }
1269             });
1270         }
1271 
1272     }
1273     else if ( typeof dataObject == 'string') {
1274         return dataObject;
1275     }
1276     return table;
1277 };
1278 
1279 vq.Hovercard.prototype.getContainer = function() {
1280     return this.hovercard;
1281 };
1282 
1283 vq.Hovercard.prototype.renderFooter = function() {
1284     var that = this;
1285     var footer = document.createElement('div');
1286     footer.setAttribute('style',"text-align:right;font-size:13px;margin-right:5px;color:rgb(240,10,10);cursor:pointer;");
1287     var close = document.createElement('span');
1288         function hideHovercard() {
1289         that.destroy();
1290         return false;
1291     }
1292     close.addEventListener('click',hideHovercard,false);
1293     close.innerHTML = 'CLOSE [X]';
1294     footer.appendChild(close);
1295     return footer;
1296 };
1297 
1298 /**
1299  *
1300  * @class provides an anchor div for a target object this is "in scope" or using the mouse cursor.
1301  *  The anchor div's location is used to instantiate a vq.Hovercard object that
1302  *  provides self_hover, moveable, pin-able and tools
1303  *
1304  *
1305  * The configuration object has the following options:
1306  *
1307  * <pre>
1308  *
1309  * {
1310  *  timeout : {Number} - number of milliseconds (ms) before the box is shown. Default is 1000,
1311  *  close_timeout : {Number} - number of milliseconds (ms) the box continues to appear after 'mouseout' - Default is 0,
1312  *  param_data : {Boolean} -  If true, the object explicitly passed into the function at event (mouseover) time is used as the
1313  *          data.  If false, the data point underlying the event source (panel, dot, etc) is used.  Default is false.
1314  *  on_mark : {Boolean} - If true, the box is placed in respect to the event source mark.  If false, the box is placed in
1315  *          respect to the cursor/mouse position.  Defaults to false.
1316  *
1317  * include_header : {Boolean} - Place Label/Value headers at top of box.  Defaults to true.
1318  * include_footer : {Boolean} - Place "Close" footer at bottom of box.  Defaults to false.
1319  * self_hover : {Boolean} - If true, the box will remain visible when the cursor is above it.  Creates the "hovercard" effect.
1320  *          The footer must be rendered to allow the user to close the box.  Defaults to false.
1321  * data_config : {Object} - Important!  This configures the content of the hovering box.  This object is identical to the
1322  *          "tooltip_items" configuration in Circvis.  Ex. { Chr : 'chr', Start : 'start', End : 'end'}.  Defaults to null
1323  * }
1324  *
1325  * </pre>
1326  *
1327  * @param opts {JSON Object} - Configuration object defined above.
1328  */
1329 
1330 pv.Behavior.hovercard = function(opts) {
1331 
1332     var hovercard, anchor_div,target,relative_div;
1333     var hovercard_div_id =  'vq_hover';
1334     var outtimer_id, clear, retry_tooltip;
1335 
1336     //list all hovercards that are visible, yet have not been persisted/pinned to the screen.
1337     function recoverHovercard() {
1338         var nodes =  document.body.childNodes;
1339         var body_length = nodes.length;
1340         var node_arr = [];
1341         for (var i =0; i< body_length; i++) {
1342             if (nodes.item(i).id && nodes.item(i).id.slice(0,'hovercard_'.length) == 'hovercard_' &&
1343                 nodes.item(i).className != "" ) {
1344                 node_arr.push(nodes.item(i));
1345             }
1346         }
1347         return node_arr;
1348     }
1349 
1350     return function(d) {
1351         var info = opts.param_data ? d : (this instanceof pv.Mark ? (this.data() ||  this.title()) : d);
1352         var mouse_x, mouse_y;
1353         var retry = opts.retry || false;
1354         var that = this;
1355         if (!retry) {
1356             target = pv.event.target;
1357             mouse_x = this.parent.mouse().x;
1358             mouse_y = this.parent.mouse().y;
1359         } else {
1360             target =opts.target;
1361             mouse_x = opts.event.x;
1362             mouse_y = opts.event.y;
1363         }
1364         opts.self_hover = true;
1365         opts.include_frame = true;
1366         opts.include_footer = true;
1367         opts.target = target;
1368         var hovercard_arr = recoverHovercard();
1369         outtimer_id = null;
1370         clear = function(){
1371             window.clearTimeout(outtimer_id);
1372         };
1373         retry_tooltip = function(){
1374             pv.Behavior.hovercard(opts).call(that,info);
1375         };
1376         //set a timeout to retry after timeout milliseconds has passed
1377         // quit if there is already a temporary hovercard on the window and timeout has passed
1378         if (hovercard_arr.length > 0 && !retry) {
1379              opts.retry = true;
1380              opts.event = {x:this.parent.mouse().x,y:this.parent.mouse().y};
1381              opts.param_data = true;
1382              d=info;
1383              target.addEventListener('mouseout',clear,false);
1384              outtimer_id = window.setTimeout(retry_tooltip,opts.timeout || 100);
1385              return;
1386        	}// if there are still cards out and this is a retry, just give up
1387         else if (hovercard_arr.length > 0 && retry) { opts.retry = false; target.removeEventListener('mouseout',clear,false); return;}
1388         else if (retry) { clear(); target.removeEventListener('mouseout',clear,false);}
1389 
1390         opts.retry = false;
1391         var t= pv.Transform.identity, p = this.parent;
1392         do {
1393             t=t.translate(p.left(),p.top()).times(p.transform());
1394         } while( p=p.parent);
1395 
1396         var c = this.root.canvas();
1397         if (!document.getElementById(c.id+'_rel')) {
1398             relative_div = vq.utils.VisUtils.createDiv(c.id+'_rel');
1399             c.insertBefore(relative_div,c.firstChild);
1400             relative_div.style.position = "relative";
1401             relative_div.style.top = "0px";
1402             relative_div.style.zIndex=-1;
1403         }
1404         else {
1405             relative_div = document.getElementById(c.id+'_rel');
1406         }
1407 
1408         if (!document.getElementById(hovercard_div_id)) {
1409             anchor_div = vq.utils.VisUtils.createDiv(hovercard_div_id);
1410             relative_div.appendChild(anchor_div);
1411             anchor_div.style.position = "absolute";
1412             anchor_div.style.zIndex = -1;
1413         }
1414         else {
1415             anchor_div = document.getElementById(hovercard_div_id);
1416             if (anchor_div.parentNode.id != relative_div.id) {
1417                 relative_div.appendChild(anchor_div);
1418             }
1419         }
1420 
1421         if(this.properties.width) {
1422             anchor_div.style.width =  opts.on_mark ? Math.ceil(this.width() * t.k) + 1 : 1;
1423             anchor_div.style.height =  opts.on_mark ? Math.ceil(this.height() * t.k) + 1 : 1;
1424         }
1425         else if (this.properties.radius) {
1426             var r = this.radius();
1427             t.x -= r;
1428             t.y -= r;
1429             anchor_div.style.height = anchor_div.style.width = Math.ceil(2 * r * t.k);
1430         }
1431         anchor_div.style.left = opts.on_mark ? Math.floor(this.left() * t.k + t.x) + "px" : Math.floor(mouse_x  * t.k+ t.x)  + "px";
1432         anchor_div.style.top = opts.on_mark ? Math.floor(this.top() * t.k + t.y) + "px" : Math.floor(mouse_y * t.k + t.y) + "px";
1433         opts.transform = t;
1434 
1435         hovercard = new vq.Hovercard(opts);
1436         hovercard.show(anchor_div,info);
1437     };
1438 };
1439 
1440 
1441 
1442 /**
1443  *
1444  * @class provides an anchor div for a target object this is "in scope" or using the mouse cursor.
1445  *  The anchor div's location is used to instantiate a vq.Hovercard object that appears onmouseover.  Not persist-able
1446  *
1447  *
1448  *
1449  * The configuration object has the following options:
1450  *
1451  * <pre>
1452  *
1453  * {
1454  *  timeout : {Number} - number of milliseconds (ms) before the box is shown. Default is 1000,
1455  *  close_timeout : {Number} - number of milliseconds (ms) the box continues to appear after 'mouseout' - Default is 0,
1456  *  param_data : {Boolean} -  If true, the object explicitly passed into the function at event (mouseover) time is used as the
1457  *          data.  If false, the data point underlying the event source (panel, dot, etc) is used.  Default is false.
1458  *  on_mark : {Boolean} - If true, the box is placed in respect to the event source mark.  If false, the box is placed in
1459  *          respect to the cursor/mouse position.  Defaults to false.
1460  *
1461  * include_header : {Boolean} - Place Label/Value headers at top of box.  Defaults to true.
1462  * include_footer : {Boolean} - Place "Close" footer at bottom of box.  Defaults to false.
1463  * self_hover : {Boolean} - If true, the box will remain visible when the cursor is above it.  Creates the "hovercard" effect.
1464  *          The footer must be rendered to allow the user to close the box.  Defaults to false.
1465  * data_config : {Object} - Important!  This configures the content of the hovering box.  This object is identical to the
1466  *          "tooltip_items" configuration in Circvis.  Ex. { Chr : 'chr', Start : 'start', End : 'end'}.  Defaults to null
1467  * }
1468  *
1469  * </pre>
1470  *
1471  * @param opts {JSON Object} - Configuration object defined above.
1472  */
1473 
1474 
1475 pv.Behavior.flextip = function(opts) {
1476 
1477     var hovercard, anchor_div,relative_div;
1478     opts.timeout = opts.timeout || 800;
1479     var hovercard_div_id =  'vq_flex';
1480 
1481     function destroyAllCards() {
1482         vq.events.Dispatcher.dispatch(new vq.events.Event('close_all_tooltips','flextip',{}));
1483     }
1484 
1485     return function(d) {
1486         var info = opts.param_data ? d : (this instanceof pv.Mark ? (this.data() ||  this.title()) : d);
1487         destroyAllCards();
1488         var t= pv.Transform.identity, p = this.parent;
1489         do {
1490             t=t.translate(p.left(),p.top()).times(p.transform());
1491         } while( p=p.parent);
1492 
1493         var c = this.root.canvas();
1494 
1495         if (!document.getElementById(c.id+'_rel')) {
1496             relative_div = vq.utils.VisUtils.createDiv(c.id+'_rel');
1497             c.insertBefore(relative_div,c.firstChild);
1498             relative_div.style.position = "relative";
1499             relative_div.style.top = "0px";
1500             relative_div.style.zIndex=-1;
1501         }
1502         else {
1503             relative_div = document.getElementById(c.id+'_rel');
1504  	    }
1505 
1506         if (!document.getElementById(hovercard_div_id)) {
1507             anchor_div = vq.utils.VisUtils.createDiv(hovercard_div_id);
1508             relative_div.appendChild(anchor_div);
1509             anchor_div.style.position = "absolute";
1510             anchor_div.style.zIndex = -1;
1511         }
1512         else {
1513             anchor_div = document.getElementById(hovercard_div_id);
1514             if (anchor_div.parentNode.id != relative_div.id) {
1515                 relative_div.appendChild(anchor_div);
1516             }
1517         }
1518         opts.include_frame = false;
1519         opts.include_footer = false;
1520         opts.target =  pv.event.target;
1521         hovercard = new vq.Hovercard(opts);
1522 
1523         if(this.properties.width) {
1524           anchor_div.style.width =  opts.on_mark ? Math.ceil(this.width() * t.k) + 1 : 1;
1525             anchor_div.style.height =  opts.on_mark ? Math.ceil(this.height() * t.k) + 1 : 1;
1526         }
1527         else if (this.properties.radius) {
1528             var r = this.radius();
1529             t.x -= r;
1530             t.y -= r;
1531             anchor_div.style.height = anchor_div.style.width = Math.ceil(2 * r * t.k);
1532         }
1533 //        var width = this.width() ? this.width() : this.properties.radius ? this.radius() * 2 : 0;
1534 //        var height = this.height() ? this.height() : this.properties.radius ? this.radius() * 2 : 0;
1535 
1536          anchor_div.style.left = opts.on_mark ? Math.floor(this.left() * t.k + t.x) + "px" : this.parent.mouse().x + t.x  + "px";
1537           anchor_div.style.top = opts.on_mark ? Math.floor(this.top() * t.k + t.y) + "px" : this.parent.mouse().y + t.y + "px";
1538 
1539         //be sure to destroy any visible flexible hovercards before displaying the current one.  Won't destroy pinned/persisted hovercards
1540         function deleteHovercard() {
1541             hovercard.destroy();
1542             vq.events.Dispatcher.removeListener('close_all_tooltips',deleteHovercard);
1543             return false;
1544         }
1545 
1546         vq.events.Dispatcher.addListener('close_all_tooltips',deleteHovercard);
1547 
1548         hovercard.show(anchor_div,info);
1549 
1550     };
1551 };
1552 
1553 
1554 
1555 
1556