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