1 /** 2 * 3 * 4 * @class Represents the circular ideogram layout. It includes all of the features of Circvis: feature glyphs/labels, 5 * ring plots, legends, and a circular network layout. The visualization is configured with a JSON object that specifies the data and 6 * interactive effects/behaviors. 7 * 8 * The JSON Object passed into the {@link CircVis#draw} function as input to the visualization: 9 * 10 * <pre> 11 * GENOME: { 12 * DATA:{ 13 * key_order : {Array} - An array of string characters denoting the keys for each ideogram ex. ['1','2','X','Y'], 14 * key_length : {Array} - An array of numbers denoting the total length of each ideogram. The total is fitted to the 15 * circumference of the circular plot ex. [3231323,2437865,2265741,1987347] 16 * }, 17 * OPTIONS: { 18 * key_reverse_list : {Array} - Array of ideogram labels to plot in the reverse (counter-clockwise) direction. 19 * eg. ['16','17']. Defaults to empty array, 20 * radial_grid_line_width : {Number} - Width of radial grid lines. Default is null (no grid lines), 21 * label_layout_style : {String} - Layout style for ideogram key labels. either 'clock' or 'default'. Default is 'default', 22 * listener : {function} - Function handler to execute on click of ideogram label, 23 * label_font_style : {String} - Ideogram label font style. ex. '20pt helvetica bold'. Defaults to '16px helvetica, monospaced'. 24 * radial_grid_line_width : {Integer} - If defined with a numeric value, ideograms will be partitioned with radial lines having the 25 * given width. 26 * } 27 * }, 28 * 29 * PLOT: { 30 * width : {Number} - total width of tool. Defaults to 400, 31 * height : {Number} - total height of tool. Defaults to 400, 32 * container : {Element or String} - Div to render the tool onto. Omission of this parameter causes the tool to be 33 * rendered to the document body. The parameter may be either the HTMLElement or the string id (eg. "circvis_div"), 34 * vertical_padding : {Number} - The number of pixels to pad above and below the rendered tool. Defaults to 0, 35 * horizontal_padding : {Number} - The number of pixels to pad to the left and right of the rendered tool. Defaults to 0, 36 * enable_pan : {Boolean} - Enable mouse drag 'pan' behavior for entire plot. Defaults to 'false', 37 * enable_zoom : {Boolean} - Enable mouse wheel 'zoom' behavior for entire plot. Defaults to 'false', 38 * show_legend : {Boolean} - Enable display of a legend for circular plot. Defaults to 'false', 39 * legend_corner : {String} - Anchor point for legend. Either ('ne','nw','se','sw') Defaults to 'ne', 40 * legend_radius : {Number} - Outer radius for legend circle plot. 41 * }, 42 * 43 * DATA:{ 44 * data_array : {Array} - An array of Circvis data nodes, ex : [ {"chr": "1", "end": 12784268, "start": 644269, 45 * "value": -0.058664}] 46 * value_key : {String} - The property of each array element that signifies the value to evaluate for the ring. This 47 * is useful for scatterplots and other types of rings. 48 * }, 49 * PLOT :{ 50 * type : The type to be drawn. Options are 'heatmap','histogram','scatterplot','karyotype'. Defaults to 'histogram', 51 * height : Radial width (in pixels) of the ring plot. Defaults to 100, 52 * 53 * }, 54 * OPTIONS: { 55 * tile_padding : {Number} Distance (in pixels) between tiles that arranged at different heights. default is 5 , 56 * tile_overlap_distance : {Number} Minimum number of base pairs of separation in order to display tiles as neighbors. default is 0.1 , 57 * tile_height : {Number} - Number of pixels in height for tile type rings. Default is 5 , 58 * tile_show_all_tiles : {Boolean} - value to determines whether to overlap tiles in order to displa them all. default : false , 59 * legend_label : {String} - optional string describing ring. Default is '', 60 * legend_description : {String} - Optional string to display when cursor is hovered over ring in legend. Default is '', 61 * draw_axes : {Boolean} - Determines whether to draw axes and axes labels on a histogram or scatterplot. Defautlts to true, 62 * outer_padding : {Integer} - Padding of outer edge of ring (in pixels), Dfault is 1, 63 * base_value : : {Number} - base value from which to plot in ring. Optional, 64 * min_value : {Number} - minimum value to plot in ring. Optional, 65 * max_value : {Number} - maximum value to plot in ring. Optional, 66 * fill_style : {Function or String} - Either a function that returns a css color string or the string itself. Default is 67 * function(d) { return d.value >= 0 ? "rgba(20,40,200,0.6)" : "rgba(200,20,40,0.6)";} }, 68 * stroke_style : {Function or String} - Either a function that returns a css color string or the string itself. Default is 69 * function(d) {return d.value >= 0 ? "#00f" : "#f00";}, 70 * shape : {Function/String} - Scatterplot glyph shape. 'triangle','diamond','cross','square','circle','tick','bar'. Defaults to 71 * function(d) {return 'circle';}, 72 * radius : {Function/Number} - scatterplot glyph radius. Defaults to function(d) {return 2;} 73 * listener : {Function} - the function to call on a click. it is passed {key,{start:{Integer},end:{Integer},value:{Number}}, 74 * tooltip_items: {Tooltip Items} - Defaults to {Chr:'chr',Start:'start',End:'end',Value:'value'}, 75 * tooltip_links: {Tooltip Links} - Displays links that can be constructed from datapoint attributes. Defaults to {} 76 * } 77 * } 78 * 79 * ], 80 * 81 * TICKS : { 82 * DATA: { 83 * data_array: {Array} - An array of Circvis data nodes where the 'value' is expected to be the label of the tick. 84 * }, 85 * OPTIONS : { 86 * height : {Number}, - The radius(in pixels) of the ring containing the tick labels/feature glyphs . Defaults to 60, 87 * listener : {Function} - A function to execute on click of a glyph. Function is passed {key, start,end, value} 88 * fill_style: {Function or CSS Color String} - A declared CSS color or a pv.Color must be the result Default is function(){return 'red';}, 89 * stroke_style : {Function or CSS Color String} - A declared CSS color or a pv.Color must be the result Default is function(){return 'white';}, 90 * display_legend: {Boolean} - Setting this to <i>true</i> will display a legend on the upper left corner mapping the tick color 91 * to the color_keys array. 92 * legend_corner : {String} - Anchor point for legend. Either ('ne','nw','se','sw') Defaults to 'nw', 93 * outer_padding : {Number} - The distance between the tool boundary and the feature glyph ring. Defaults to 0, 94 * overlap_distance : {Number} - Used in tiling of the feature glyphs. The maximum distance (in base pairs) that constitutes overlapping features. Defaults to 7000000.0 95 * tile_ticks : {Boolean} - Declare whether to tile feature glyphs or lay all of them out at the same level. Defaults to true, 96 * label_key : {String} - The property of the feature data which denotes the feature label. eg. {label:'GENEA'} would require label_key: 'label' 97 * label_map : {Array} - A mapping of an example feature label to a string describing that feature type. Each element is an object. 98 * eg. [{key:'TP53', label:'Gene'}]. Default is no mapping. 99 * wedge_height : {Number}, - The height(in pixels) of each tick/feature glyphs . Defaults to 10, 100 * wedge_width : {Number}, - The width(in degrees) of each tick/feature glyphs . Defaults to 0.5, 101 102 * tooltip_items : {Tooltip Object} - JSON object that configures the tooltip display of a feature glyph. The mapping of each property is: 103 * tooltip label : 'data_point_property'. 104 * The data_point_property can be statically defined or be a function that operates on the data point and returns a string. Default is: 105 * { Chr : 'chr', Start : 'start', End : 'end', Label:'value'} 106 * tooltip_links: {Tooltip Links} - Displays links that can be constructed from datapoint attributes. Defaults to {} 107 * } 108 * }, 109 * 110 * NETWORK:{ 111 * DATA:{ 112 * data_array : {Array} - An array of Circvis network objects, which consist of 2 data nodes, a linkValue, and a linkOptions 113 * }, 114 * OPTIONS: { 115 * node_listener : {Function} - function to execute on click on a node. Default is function() {return null;}, 116 * link_listener : {Function{ - function to execute on click of a link. Default is function() {return null;}, 117 * link_alpha : {Number or Function} - a value between (0.0,1.0) which defines the link 'alpha' for a given edge. Default is function() {return 0.7;}, 118 * link_line_width : {Number or Function} - link width in units of pixels. Default is function(node,link) { return 1; }, 119 * node_fill_style : {Function or String} - Either a function that returns a css color string, the string itself, or 'ticks'. Default is 120 * function() { return 'blue';}, 121 * link_stroke_style : {Function or String} - Either a function that returns a css color string or the string itself. Default is function() { return 'red';}, 122 * tile_nodes : {Boolean} - Determines whether network nodes are 'tiled' to prevent visual occlusion by proximal features. Defaults to 'false'. 123 * node_key : {Function} - Function that accepts a node object argument and returns the ideogram 'key' label. Defaults to function(node) { return node['chr'];}, 124 * max_node_linkdegree : {Number} - A number greater than zero which filters out all nodes with a higher linkDegree ex. 100. Defaults to 9999, 125 * min_node_linkdegree : {Number} - A number greater than zero which filters out all nodes with a lower linkDegree ex. 2. Defaults to 0, 126 * node_overlap_distance : {Number} - When tiling the graph nodes, the maximum distance (in base pairs) that constitutes overlapping tiles. Defaults to 12000000.0 127 * node_tooltip_items : {Tooltip Object} - Defaults to { Chr : 'chr', Start : 'start', End : 'end'}, 128 * link_tooltip_items : {Tooltip Object} - Defaults to { 'Node 1 Chr' : 'sourceNode.chr', 'Node 1 Start' : 'sourceNode.start', 'Node1 End' : 'sourceNode.end', 129 * 'Node 2 Chr' : 'targetNode.chr', 'Node 2 Start' : 'targetNode.start', 'Node 2 End' : 'targetNode.end'}, 130 * node_tooltip_links: {Tooltip Links} - Displays links that can be constructed from datapoint attributes. Defaults to {}, 131 * link_tooltip_links: {Tooltip Links} - Displays links that can be constructed from datapoint attributes. Defaults to {} 132 * 133 * } 134 * } 135 * 136 * }; 137 * </pre> 138 * @extends vq.Vis 139 */ 140 vq.CircVis = function() { 141 vq.Vis.call(this); 142 }; 143 vq.CircVis.prototype = pv.extend(vq.Vis); 144 145 vq.CircVis.prototype.setPanEnabled = function(pan_enabled) { 146 if (arguments.length < 1) { return; } 147 var enable_pan = Boolean(pan_enabled); 148 this.chromoData._plot.enable_pan = enable_pan; 149 if ( enable_pan) {this.event_panel.cursor('move'); this.event_panel.event("mousedown",pv.Behavior.pan());} 150 else { 151 if (!this.chromoData._plot.enable_zoom) {this.event_panel.cursor('default'); } 152 this.event_panel.event("mousedown",null); 153 } 154 }; 155 156 /** 157 * Include/Remove the zoom effect using a mousewheel 158 * 159 * @param {Boolean} zoom_enabled - the index of the ring to set the value for. Zero is the outermost ring. 160 */ 161 162 vq.CircVis.prototype.setZoomEnabled = function(zoom_enabled) { 163 if (arguments.length < 1) { return; } 164 var enable_zoom = Boolean(zoom_enabled); 165 this.chromoData._plot.enable_zoom = enable_zoom; 166 if ( enable_zoom) {this.event_panel.cursor('move'); this.event_panel.event("mousewheel",pv.Behavior.zoom());} 167 else { 168 if (!this.chromoData._plot.enable_pan) {this.event_panel.cursor('default'); } 169 this.event_panel.event("mousewheel",null); 170 } 171 }; 172 173 /** 174 * Set the maximum value on the y-axis of a ring plot. 175 * 176 * @param {Integer} ring_number - the index of the ring to set the value for. Zero is the outermost ring. 177 * @param {Number} max_value - the value to assign as the maximum to plot. 178 */ 179 180 vq.CircVis.prototype.setMaxPlotValue = function(wedge_number,value) { 181 if ( value > this.chromoData._wedge[wedge_number]._min_plotValue){ 182 this.chromoData._wedge[wedge_number]._max_plotValue = value; 183 this._render(); 184 } 185 }; 186 /** 187 * Set the minimum value on the y-axis of a ring plot. 188 * 189 * @param {Integer} ring_number - the index of the ring to set the value for. Zero is the outermost ring. 190 * @param {Number} min_value - the value to assign as the minimum to plot. 191 */ 192 193 194 vq.CircVis.prototype.setMinPlotValue = function(wedge_number,value) { 195 if ( value < this.chromoData._wedge[wedge_number]._max_plotValue){ 196 this.chromoData._wedge[wedge_number]._min_plotValue = value; 197 this._render(); 198 } 199 }; 200 201 /** 202 * Set the maximum link degree for which a node will be displayed 203 * 204 * @param {Number} max_linkDegree - the value to assign as the maximum link degree allowable. 205 */ 206 207 vq.CircVis.prototype.setMaxNodeLinkDegree = function(value) { 208 if ( value > this.chromoData._network.min_node_linkDegree){ 209 this.chromoData._network.max_node_linkDegree = value; 210 this._render(); 211 } 212 }; 213 214 /** 215 * Set the minimum link degree for which a node will be displayed 216 * 217 * @param {Number} min_linkDegree - the value to assign as the minimum link degree allowable. 218 */ 219 vq.CircVis.prototype.setMinNodeLinkDegree = function(value) { 220 if ( value < this.chromoData._network.max_node_linkDegree){ 221 this.chromoData._network.min_node_linkDegree = value; 222 this._render(); 223 } 224 }; 225 /** 226 * Changes the plot size and re-renders the plot. 227 * 228 * @param {Number} height - plot height in units of pixels 229 * @param {Number} width - plot width in units of pixels 230 */ 231 232 vq.CircVis.prototype.setSize = function(height, width) { 233 if (height > 1 && width > 1 && (width != this.width() && height != this.height())) { 234 this.width(width); 235 this.height(height); 236 this._render(); 237 } 238 }; 239 240 /** 241 * Highlight the network and tick corresponding to the requested tick. Passing the empty string('') causes the 242 * highlighting to be turned off. 243 * 244 * @param {String} label - the node id (commonly the tick label string) of the feature to highlight 245 */ 246 247 vq.CircVis.prototype.selectNodeLabel = function(label) { 248 if (label == '') //empty all active lists 249 { 250 this.chromoData.network_panel.activeNetworkNode(null); 251 this.chromoData.network_panel.render(); 252 this.chromoData.tick_panel.activeTickList([]); 253 this.chromoData.tick_panel.render(); 254 return; 255 } 256 257 this.chromoData._network.selectedLabel = label; 258 var match = this.chromoData._network.nodes_array.map(function(c) { return vq.utils.VisUtils.network_node_id(c);}) 259 .filter(function(c) { return c == label;}); 260 if (match.length > 0) { 261 this.chromoData.network_panel.activeNetworkNode(label); 262 this.chromoData.network_panel.render(); 263 } 264 var list = this.chromoData.tick_panel.activeTickList(); 265 list.push(label); 266 this.chromoData.tick_panel.activeTickList(list); 267 this.chromoData.tick_panel.render(); 268 }; 269 270 /** @private **/ 271 272 vq.CircVis.prototype._setOptionDefaults = function(options) { 273 274 if (options.height != null) {this.height(options.height); } 275 276 if (options.width != null) { this.width(options.width); } 277 278 if (options.vertical_padding != null) { this.vertical_padding(options.vertical_padding); } 279 280 if (options.horizontal_padding != null) { this.horizontal_padding(options.horizontal_padding);} 281 282 if (options.container) { this.container(options.container); } 283 284 }; 285 /** 286 * 287 * Constructs the Circvis model and adds the SVG tags to the defined DOM element. 288 * 289 * @param {JSON Object} circvis_object - the object defined above. 290 */ 291 vq.CircVis.prototype.draw = function(data) { 292 293 var vis_data = new vq.models.CircVisData(data); 294 295 if (vis_data.isDataReady()) { 296 this._setOptionDefaults(vis_data); 297 this.chromoData = vis_data; 298 this._render(); 299 } else { 300 console.warn('Invalid data input. Check data for missing or improperly formatted values.'); 301 } 302 }; 303 304 /** private **/ 305 vq.CircVis.prototype._add_ticks = function(outerRadius) { 306 var dataObj = this.chromoData; 307 var outerTickRadius = outerRadius - dataObj.ticks.outer_padding; 308 var innerRadius = outerTickRadius - dataObj.ticks.height; 309 var inner = dataObj.ticks.tile_ticks ? function(feature) { return innerRadius + (feature.level * (dataObj.ticks.wedge_height * 1.3)) ;} : 310 function(feature) { return innerRadius;}; 311 var outer = function(feature) { return inner(feature) + dataObj.ticks.wedge_height;}; 312 var feature_angle_map = function(c,d) {return dataObj.startAngle_map[d] + dataObj.theta[d](c.start); }; 313 var tick_fill = function(c) { return pv.color(dataObj.ticks.fill_style(c));}; 314 var tick_stroke = function(c) { return pv.color(dataObj.ticks.stroke_style(c));}; 315 var label_key = dataObj.ticks.label_key; 316 317 var tick_angle = function(tick) { var angle = tick_length / inner(tick); return isNodeActive(tick) ? angle * 2 : angle; }; 318 var isNodeActive = function(c) { return ( c.active || 319 (dataObj.tick_panel.activeTickList().filter(function(d) { return d == c[label_key];}).length > 0));}; 320 321 var tick_width = Math.PI / 180 * dataObj.ticks.wedge_width; 322 var tick_length = tick_width * innerRadius; 323 324 dataObj.tick_panel = this.event_panel.add(pv.Panel) 325 .def('activeTickList',[]) 326 .fillStyle(null) 327 .data(dataObj._chrom.keys); 328 329 var behavior = function(d) { 330 return (pv.Behavior.hovercard( 331 { 332 include_header : false, 333 include_footer : true, 334 self_hover : true, 335 timeout : dataObj._plot.tooltip_timeout, 336 data_config : dataObj.ticks.tooltipItems, 337 tool_config : dataObj.ticks.tooltipLinks 338 } 339 ).call(this,d), 340 d.active = 1, 341 this.render(), 342 this.parent.children[1].render());}; 343 344 dataObj.tick_panel.add(pv.Wedge) 345 .events("all") 346 .data(function(d) { return dataObj.ticks.data_map[d]; } ) 347 .angle(tick_angle) 348 .startAngle(function(c,d) { return feature_angle_map(c,d);}) 349 .innerRadius(inner) 350 .outerRadius(outer) 351 .event("mouseout", function(c) { c.active = 0; this.render(); this.parent.children[1].render(); }) 352 .event('mouseover',behavior) 353 .event("click", function(c) { dataObj.ticks.listener(c); }) 354 .cursor('pointer') 355 .strokeStyle(function(c) { return tick_stroke(c);}) 356 .lineWidth(1) 357 .fillStyle(function(c) { return tick_fill(c);}) 358 .anchor("inner").add(pv.Label) 359 .text(function(c) { return isNodeActive(c) ? c[label_key] : ""; }) 360 .font('14px helvetica'); 361 362 if (dataObj.ticks.display_legend){ 363 364 var corner = dataObj.ticks.legend_corner; 365 366 var legend = this.event_panel.add(pv.Panel) 367 .height(dataObj.ticks.label_map.length * 14) 368 .width(60) 369 .title('Tick Legend'); 370 371 switch(corner) { 372 case 'ne' : 373 legend.right(0).top(0); 374 break; 375 case 'se' : 376 legend.right(0).bottom(0); 377 break; 378 case 'sw' : 379 legend.left(0).bottom(0); 380 break; 381 case 'nw' : 382 default : 383 legend.left(0).top(0); 384 } 385 386 legend.add(pv.Label) 387 .top(10) 388 .left(10) 389 .font("11px helvetica") 390 .text("Tick Legend"); 391 legend.add(pv.Bar) 392 .data(dataObj.ticks.label_map) 393 .left(10) 394 .top(function() {return 20 + 15*this.index;} ) 395 .fillStyle(function(d) {return tick_fill(d.key);}) 396 .width(36) 397 .height(12) 398 .anchor("right").add(pv.Label) 399 .text(function(d) { return d.label;}) 400 .font("11px helvetica") 401 .textMargin(6) 402 .textAlign("left"); 403 } 404 }; 405 406 /**private **/ 407 vq.CircVis.prototype._add_wedge = function(index,outerRadius) { 408 var dataObj = this.chromoData, dot; 409 var width = this.width(), height = this.height(); 410 var outerPlotRadius = outerRadius - dataObj._wedge[index]._outer_padding; 411 var innerRadius = outerPlotRadius - dataObj._wedge[index]._plot_height; 412 413 this.wedge_layer[index] = this.event_panel.add(pv.Wedge) 414 .data(dataObj._chrom.keys) 415 .left(width/2) 416 .top(height/2) 417 .innerRadius(innerRadius) 418 .outerRadius(outerPlotRadius) 419 .angle(function(d) { return dataObj.normalizedLength[d] * 2 * Math.PI;} ) 420 .startAngle(function(d) { return dataObj.startAngle_map[d]; } ) 421 .fillStyle("#ddd") 422 .strokeStyle("#444") 423 .lineWidth(1) 424 .overflow("hidden") 425 .add(pv.Wedge) 426 .innerRadius(innerRadius) 427 .outerRadius(outerPlotRadius) 428 .lineWidth(1) 429 .strokeStyle("#444"); 430 431 if ((dataObj._wedge[index]._plot_type != 'karyotype') && 432 (dataObj._wedge[index]._plot_type != 'tile') && 433 (dataObj._wedge[index]._plot_type != 'band') && 434 (dataObj._wedge[index]._plot_type != 'glyph')) { 435 if (isNaN(dataObj._wedge[index]._min_plotValue) || isNaN(dataObj._wedge[index]._max_plotValue)) { 436 console.warn('Range of values for ring with index (' + index +') not detected. Data has not been plotted.'); 437 return; 438 } 439 } 440 441 if (dataObj._wedge[index]._min_plotValue == dataObj._wedge[index]._max_plotValue) { 442 dataObj._wedge[index]._min_plotValue = dataObj._wedge[index]._min_plotValue - 1; 443 dataObj._wedge[index]._max_plotValue = dataObj._wedge[index]._max_plotValue + 1; 444 } 445 446 var range_mean = dataObj._wedge[index]._base_plotValue != null ? dataObj._wedge[index]._base_plotValue : 447 (dataObj._wedge[index]._min_plotValue+ dataObj._wedge[index]._max_plotValue) / 2; 448 var y_axis = pv.Scale.linear(dataObj._wedge[index]._min_plotValue, dataObj._wedge[index]._max_plotValue).range(innerRadius,outerPlotRadius); 449 var thresholded_innerRadius = function(d) { return Math.max(y_axis(Math.min(d,range_mean)),innerRadius); }; 450 var thresholded_outerRadius = function(d) { return Math.min(y_axis(Math.max(d,range_mean)),outerPlotRadius); }; 451 var thresholded_value_to_radius = function(d) { return Math.min(Math.max(y_axis(d),innerRadius),outerPlotRadius); }; 452 var thresholded_radius = function(d) { return Math.min(Math.max(d,innerRadius),outerPlotRadius); }; 453 var thresholded_tile_innerRadius = function(c,d) { return innerRadius + (d._tile_height + d._tile_padding) * c.level;}; 454 var thresholded_tile_outerRadius = function(c,d) { return innerRadius + ((d._tile_height + d._tile_padding) * c.level) + d._tile_height;}; 455 var glyph_distance = function(c,d) { return (((d._tile_height + d._tile_padding) * c.level) 456 + innerRadius + (d._radius() * 2));}; 457 var checked_endAngle = function(c,d) { 458 if (dataObj._chrom.keys.length == 1) { 459 return Math.min(dataObj.startAngle_map[d] + dataObj.theta[d](c.end||c.start+1),dataObj.startAngle_map[dataObj._chrom.keys[0]] + (Math.PI * 2)); 460 } 461 else if (this.parent.index+1 == dataObj._chrom.keys.length) { 462 return Math.min(dataObj.startAngle_map[d] + dataObj.theta[d](c.end||c.start+1),dataObj.startAngle_map[dataObj._chrom.keys[0]] + (Math.PI * 2)); 463 } 464 else {return Math.min(dataObj.startAngle_map[d] + dataObj.theta[d](c.end||c.start+1), 465 dataObj.startAngle_map[dataObj._chrom.keys[(this.parent.index+1)%dataObj._chrom.keys.length]]); 466 } 467 }; 468 var feature_angle = function(d) { return dataObj.startAngle_map[d.chr] + dataObj.theta[d.chr](d.start); }; 469 470 var value_key = dataObj._wedge[index]._value_key; 471 472 var behavior = function(d) { 473 return (pv.Behavior.hovercard( 474 { 475 include_header : false, 476 include_footer : true, 477 self_hover : true, 478 timeout: dataObj._plot.tooltip_timeout, 479 data_config : 480 dataObj._wedge[index]._tooltipItems, 481 tool_config : dataObj._wedge[index]._tooltipLinks 482 } 483 ).call(this,d));}; 484 //add a new panel each time we want to draw on top of the previously created image. 485 var panel_layer = this.event_panel.add(pv.Panel) 486 .fillStyle(null) 487 .data(dataObj._chrom.keys); 488 489 switch (dataObj._wedge[index]._plot_type) { 490 case 'histogram': 491 if(dataObj._wedge[index]._draw_axes) { 492 /* Circular grid lines. */ 493 dot = this.event_panel.add(pv.Dot) 494 .data(y_axis.ticks(4)) 495 .fillStyle(null) 496 .strokeStyle("#444") 497 .lineWidth(1) 498 .radius(function(i) { return y_axis(i); } ); 499 dot.anchor("top").add(pv.Label) 500 .textBaseline("middle") 501 .textAlign("right") 502 .text(function(i) {return y_axis.tickFormat(i);}); 503 dot.anchor("bottom").add(pv.Label) 504 .textBaseline("middle") 505 .textAlign("right") 506 .text(function(i) {return y_axis.tickFormat(i);}); 507 } 508 panel_layer.add(pv.Wedge) //histogram 509 .data(function(d) { return dataObj._wedge[index]._chr_map[d];}) 510 .startAngle(function(c,d) { return dataObj.startAngle_map[d] + dataObj.theta[d](c.start); }) 511 .endAngle(checked_endAngle) 512 .innerRadius(function(c) { return thresholded_innerRadius(c[value_key]);} ) 513 .outerRadius(function(c) { return thresholded_outerRadius(c[value_key]); } ) 514 .strokeStyle(dataObj._wedge[index]._strokeStyle) 515 .fillStyle(dataObj._wedge[index]._fillStyle) 516 .cursor('pointer') 517 .event('click',function(c,d){ dataObj._wedge[index].listener(c);} ) 518 .event('mouseover',behavior); 519 break; 520 case 'scatterplot': 521 if(dataObj._wedge[index]._draw_axes) { 522 dot = this.event_panel.add(pv.Dot) 523 .data(y_axis.ticks(4)) 524 .fillStyle(null) 525 .strokeStyle("#444") 526 .lineWidth(1) 527 .radius(function(i) { return y_axis(i); } ); 528 dot.anchor("top").add(pv.Label) 529 .textBaseline("middle") 530 .textAlign("right") 531 .text(function(i) {return y_axis.tickFormat(i);}); 532 dot.anchor("bottom").add(pv.Label) 533 .textBaseline("middle") 534 .textAlign("right") 535 .text(function(i) {return y_axis.tickFormat(i);}); 536 } 537 panel_layer.add(pv.Dot) //scatterplot 538 .data(function(d) { return dataObj._wedge[index]._chr_map[d];}) 539 .left(function(c,d) { return width/2 + (thresholded_value_to_radius(c[value_key]) * Math.cos(feature_angle(c))); }) 540 .bottom(function(c,d) { return height/2 + (-1 * (thresholded_value_to_radius(c[value_key])) * Math.sin(feature_angle(c))); }) 541 .shape(dataObj._wedge[index]._shape) 542 .radius(dataObj._wedge[index]._radius) 543 .strokeStyle(dataObj._wedge[index]._strokeStyle) 544 .fillStyle(dataObj._wedge[index]._fillStyle) 545 .cursor('pointer') 546 .event('click',function(c,d){ dataObj._wedge[index].listener(c);} ) 547 .event('mouseover',behavior); 548 break; 549 case 'glyph': 550 panel_layer.add(pv.Dot) //glyph 551 .data(function(d) { return dataObj._wedge[index]._chr_map[d];}) 552 .left(function(c,d) { return width/2 + (glyph_distance(c,dataObj._wedge[index])) * Math.cos(feature_angle(c)); }) 553 .bottom(function(c,d) { return height/2 + (-1 * glyph_distance(c,dataObj._wedge[index]) * Math.sin(feature_angle(c))); }) 554 .shape(dataObj._wedge[index]._shape) 555 .radius(dataObj._wedge[index]._radius) 556 .strokeStyle(dataObj._wedge[index]._strokeStyle) 557 .fillStyle(dataObj._wedge[index]._fillStyle) 558 .cursor('pointer') 559 .event('click',function(c,d){ dataObj._wedge[index].listener(c);} ) 560 .event('mouseover',behavior); 561 break; 562 case 'band': 563 panel_layer.add(pv.Wedge) //tile 564 .data(function(d) { return dataObj._wedge[index]._chr_map[d];}) 565 .startAngle(function(c,d) { return dataObj.startAngle_map[d] + dataObj.theta[d](c.start); }) 566 .endAngle(checked_endAngle) 567 .innerRadius(innerRadius ) 568 .outerRadius(outerPlotRadius ) 569 .strokeStyle(dataObj._wedge[index]._strokeStyle) 570 .fillStyle(dataObj._wedge[index]._fillStyle) 571 .cursor('pointer') 572 .event('click',function(c,d){ dataObj._wedge[index].listener(c);} ) 573 .event('mouseover',behavior); 574 break; 575 case 'tile': 576 panel_layer.add(pv.Wedge) //tile 577 .data(function(d) { return dataObj._wedge[index]._chr_map[d];}) 578 .startAngle(function(c,d) { return dataObj.startAngle_map[d] + dataObj.theta[d](c.start); }) 579 .endAngle(checked_endAngle) 580 .innerRadius(function(c,d) { return thresholded_tile_innerRadius(c,dataObj._wedge[index]);} ) 581 .outerRadius(function(c,d) { return thresholded_tile_outerRadius(c,dataObj._wedge[index]);} ) 582 .strokeStyle(dataObj._wedge[index]._strokeStyle) 583 .fillStyle(dataObj._wedge[index]._fillStyle) 584 .cursor('pointer') 585 .event('click',function(c,d){ dataObj._wedge[index].listener(c);} ) 586 .event('mouseover',behavior); 587 break; 588 case 'heatmap': 589 panel_layer.add(pv.Wedge) //heatmap plot of cnv 590 .data(function(d) { return dataObj._wedge[index]._chr_map[d];}) 591 .startAngle(function(c,d) { return dataObj.startAngle_map[d] + dataObj.theta[d](c.start); }) 592 .endAngle(function(c,d) { 593 if (this.parent.index+1 == dataObj._chrom.keys.length) { return dataObj.startAngle_map[dataObj._chrom.keys[0]] + (Math.PI * 2);} 594 else {return Math.min(dataObj.startAngle_map[d] + dataObj.theta[d](c.end||c.start+1), 595 dataObj.startAngle_map[dataObj._chrom.keys[(this.parent.index+1)%dataObj._chrom.keys.length]]); 596 } 597 }) 598 .innerRadius(thresholded_innerRadius(dataObj._wedge[index]._min_plotValue) ) 599 .outerRadius(thresholded_outerRadius(dataObj._wedge[index]._max_plotValue) ) 600 .strokeStyle(dataObj._wedge[index]._strokeStyle) 601 .fillStyle(dataObj._wedge[index]._fillStyle) 602 .cursor('pointer') 603 .event('click',function(c,d){ dataObj._wedge[index].listener(c);} ) 604 .event('mouseover',behavior); 605 break; 606 case 'karyotype': 607 panel_layer.add(pv.Wedge) //karyotype 608 .data(function(d) { return dataObj._wedge[index]._chr_map[d];}) 609 .startAngle(function(c,d) { return dataObj.startAngle_map[d] + dataObj.theta[d](c.start); }) 610 .endAngle(checked_endAngle) 611 .innerRadius(innerRadius ) 612 .outerRadius(outerPlotRadius ) 613 .fillStyle( function(d) {return d[value_key];}) 614 .cursor('pointer') 615 .event('click',function(c,d){ dataObj._wedge[index].listener(c);} ) 616 .event('mouseover',behavior); 617 break; 618 default: 619 console.warn('No plot type definition detected.'); 620 } 621 622 }; 623 624 /** private **/ 625 vq.CircVis.prototype._add_network = function () { 626 var dataObj = this.chromoData, 627 w = this.width(), 628 h = this.height(); 629 var network_radius = dataObj._network.radius; 630 var node_behavior = function(d) { 631 return (pv.Behavior.hovercard( 632 { 633 include_header : false, 634 include_footer : false, 635 self_hover : true, 636 timeout : dataObj._plot.tooltip_timeout, 637 data_config : 638 dataObj._network.node_tooltipItems, 639 tool_config : dataObj._network.node_tooltipLinks 640 } 641 ).call(this,d), 642 643 dataObj.network_panel.activeNetworkNode(this.index), 644 populateConnectedNodes(this.index), 645 dataObj.network_panel.render() 646 );}; 647 648 var link_behavior = function(c,d) { 649 return (pv.Behavior.hovercard( 650 { 651 include_header : false, 652 include_footer : false, 653 self_hover : true, 654 param_data : true, 655 timeout : dataObj._plot.tooltip_timeout, 656 data_config : 657 dataObj._network.link_tooltipItems, 658 tool_config : 659 dataObj._network.link_tooltipLinks 660 } 661 ).call(this,d), 662 663 dataObj.network_panel.activeNetworkLink(this.parent.index), 664 dataObj.network_panel.render() 665 );}; 666 667 var feature_angle = function(d) { return dataObj.startAngle_map[d.chr] + dataObj.theta[d.chr](d.start); }; 668 669 var network_node_y = dataObj._network.tile_nodes ? 670 function(d) { return h/2 + (-1 * (network_radius - (d.level * 10)) * Math.sin(feature_angle(d))); } : 671 function(d) { return h/2 + (-1 * (network_radius) * Math.sin(feature_angle(d))) }; 672 var network_node_x = dataObj._network.tile_nodes ? 673 function(d) { return w/2 + ((network_radius - (d.level * 10)) * Math.cos(feature_angle(d))); } : 674 function(d) { return w/2 + ((network_radius) * Math.cos(feature_angle(d))); }; 675 var node_angle = function(d) { return feature_angle(d) + Math.PI /2;}; 676 677 var link_color = function(link) {return pv.color(dataObj._network.link_strokeStyle(link));}; 678 /** @private */ 679 var node_colors; 680 681 if (dataObj._network.node_fillStyle() == 'ticks') { 682 node_colors = function(node) { return dataObj.ticks.fill_style(node);}; 683 } else { 684 node_colors = function(node) {return pv.color(dataObj._network.node_fillStyle(node));}; 685 } 686 687 var node_stroke = function(node) {return pv.color(dataObj._network.node_strokeStyle(node));}; 688 689 var link_active = function(c,d) {return (dataObj.network_panel.activeNetworkNode() == null || 690 this.parent.index == dataObj.network_panel.activeNetworkLink() || 691 d.source == dataObj.network_panel.activeNetworkNode() || 692 d.target == dataObj.network_panel.activeNetworkNode()) && 693 (linkDegreeInBounds(d.sourceNode) || linkDegreeInBounds(d.targetNode));}; 694 695 var link_width_active = function(node, link) { 696 return (this.parent.index == dataObj.network_panel.activeNetworkLink() || 697 link.source == dataObj.network_panel.activeNetworkNode() || 698 link.target == dataObj.network_panel.activeNetworkNode() ) ? 699 dataObj._network.link_line_width(node, link) + 1.0 : dataObj._network.link_line_width(node, link); 700 }; 701 702 function link_angle(node, link) { 703 return (feature_angle(link.sourceNode) - feature_angle(link.targetNode) <= -1 * Math.PI) ? "polar" : 704 (feature_angle(link.sourceNode) - feature_angle(link.targetNode) >= Math.PI) ? "polar-reverse" : 705 (feature_angle(link.sourceNode) - feature_angle(link.targetNode) >= 0) ? "polar" : "polar-reverse"; 706 } 707 708 function link_eccentricity(c, d) { 709 return Math.round(Math.pow(Math.sin(Math.abs(feature_angle(d.sourceNode) - feature_angle(d.targetNode))/ 2 ) ,4)*100)/100; 710 } 711 712 var link_visible = function(c,d) { return true;}; 713 714 switch(dataObj._network.node_highlightMode) { 715 case('isolate'): 716 link_visible = link_active; 717 break; 718 case('brighten'): 719 default: 720 } 721 722 function link_strokeStyle(c,d) { 723 return (this.parent.index == dataObj.network_panel.activeNetworkLink() || 724 d.source == dataObj.network_panel.activeNetworkNode() || 725 d.target == dataObj.network_panel.activeNetworkNode() ) ? 726 link_color(d).darker(2).alpha(dataObj._network.link_alpha(d)) : 727 link_color(d).alpha(dataObj._network.link_alpha(d) ); 728 } 729 function linkDegreeInBounds(node) { 730 return ( dataObj._network.min_node_linkDegree == null ? true : node.linkDegree >= dataObj._network.min_node_linkDegree) && 731 ( dataObj._network.max_node_linkDegree == null ? true : node.linkDegree <= dataObj._network.max_node_linkDegree); 732 } 733 734 function populateConnectedNodes(nodes_array_index) { 735 var nodes1 = dataObj._network.links_array.filter(function(d) { 736 return d.source == nodes_array_index;}).map(function(b) { 737 return {node:b.targetNode,linkValue:b.linkValue}; 738 }); 739 var nodes2 = dataObj._network.links_array.filter(function(d) { 740 return d.target == nodes_array_index;}).map(function(b) { 741 return {node:b.sourceNode,linkValue:b.linkValue}; 742 }); 743 dataObj.network_panel.connectedToActiveNetworkNode(nodes1.concat(nodes2)); 744 } 745 746 function link_listener(c,link) { 747 dataObj._network.link_listener(link); 748 } 749 750 dataObj.network_panel = this.event_panel.add(pv.Layout.Network) 751 .def('connectedToActiveNetworkNode', []) 752 .def('activeNetworkNode', null) 753 .def('activeNetworkLink', null) 754 .nodes(dataObj._network.nodes_array) 755 .links(dataObj._network.links_array); 756 757 dataObj.network_panel.link.add(pv.Line) 758 .visible(link_visible) 759 .interpolate(link_angle) 760 .strokeStyle(link_strokeStyle) 761 .eccentricity(link_eccentricity) 762 .cursor('pointer') 763 .event('mouseover',link_behavior) 764 .event('mouseout', function() { 765 dataObj.network_panel.activeNetworkLink(null); 766 dataObj.network_panel.render(); 767 }) 768 .event('click', link_listener) 769 .lineWidth(link_width_active); 770 771 dataObj.network_panel.node 772 .bottom(network_node_y) 773 .left(network_node_x) 774 .fillStyle(function(c,d) { return node_colors(c).alpha(0.9); }) 775 .strokeStyle(function(c) { return node_stroke(c).alpha(0.9); }); 776 777 dataObj.network_panel.node.add(pv.Dot) 778 .shape('dot') 779 .lineWidth(1) 780 .radius(2.0) 781 .angle(node_angle) 782 .event('mouseover',node_behavior) 783 //.title(dataObj._network.node_tooltipFormat) 784 .event('mouseout', function() { 785 dataObj.network_panel.activeNetworkNode(null); 786 dataObj.network_panel.connectedToActiveNetworkNode([]); 787 dataObj.network_panel.render(); 788 }) 789 .cursor('pointer') 790 .event('click', function(c) { 791 dataObj._network.node_listener(c, dataObj.network_panel.connectedToActiveNetworkNode()); 792 }); 793 }; 794 795 /** private **/ 796 vq.CircVis.prototype._add_legend = function() { 797 var dataObj = this.chromoData, 798 h = this.height(), 799 w = this.width(); 800 801 var radius = dataObj._plot.legend_radius, 802 diameter = radius * 2, 803 corner = dataObj._plot.legend_corner; 804 805 var legend = this.event_panel.add(pv.Panel) 806 .width(diameter+20) 807 .height(diameter+(dataObj._wedge.length*10)) 808 .title('Legend'); 809 var rings = legend.add(pv.Panel) 810 .bottom(0) 811 .left(0) 812 .data(pv.range(0,dataObj._wedge.length)); 813 814 switch(corner) { 815 case 'ne' : 816 legend.right(0).top(0); 817 break; 818 case 'se' : 819 legend.right(0).bottom(0); 820 break; 821 case 'sw' : 822 legend.left(0).bottom(0); 823 break; 824 case 'nw' : 825 default : 826 legend.left(0).top(0); 827 } 828 829 var legend_color = pv.Colors.category10(0,dataObj._wedge.length); 830 var legend_rings = radius - 5; 831 var ring_width = Math.min(legend_rings / dataObj._wedge.length, 5); 832 var ring_space = 2; 833 834 var legend_outerRadius = function(i) { 835 return radius - (i*(ring_width + ring_space)) ; 836 }; 837 var legend_innerRadius = function(i) { 838 return Math.min((legend_outerRadius(i) - ring_width),legend_outerRadius(i) - 2); 839 }; 840 841 if(dataObj._plot.legend_show_rings) { 842 rings.add(pv.Wedge) 843 .outerRadius(legend_outerRadius) 844 .innerRadius(legend_innerRadius) 845 .title(function(c) { return dataObj._wedge[c]._legend_desc;}) 846 .lineWidth(0) 847 .angle(Math.PI * 2) 848 .fillStyle(legend_color) 849 .strokeStyle(legend_color) 850 .left(radius+10) 851 .bottom(radius); 852 } 853 rings.add(pv.Bar) 854 .top(function(c) {return c*10;} ) 855 .height(10) 856 .title(function(c) { return dataObj._wedge[c]._legend_desc;}) 857 .fillStyle(null) 858 .lineWidth(0) 859 .strokeStyle(null) 860 .add(pv.Label) 861 .textStyle(legend_color) 862 .textAlign('center') 863 .font("11px helvetica") 864 .text(function(c) { return dataObj._wedge[c]._legend_label;}); 865 }; 866 867 /** private **/ 868 869 vq.CircVis.prototype._render = function() { 870 var dataObj = this.chromoData, 871 w = this.width(), 872 h = this.height(); 873 874 var container = this.container(); 875 var outerRadius = h/2; 876 var verticalPadding = this.vertical_padding(), horizontalPadding = this.horizontal_padding(); 877 878 879 this.vis = new pv.Panel() 880 .width(w) 881 .height(h) 882 .bottom(verticalPadding) 883 .left(horizontalPadding) 884 .right(horizontalPadding) 885 .top(verticalPadding) 886 .fillStyle(null) 887 .canvas(container); 888 889 this.event_panel = this.vis.add(pv.Panel) 890 .events('all') 891 .data([{x:0, y: 0, dx:w, dy:h}]) 892 .left(function(d) { return d.x;}) 893 .fillStyle(null) 894 .bottom(function(d) { return d.y}); 895 if ( dataObj._plot.enable_pan) {this.event_panel.event("mousedown",pv.Behavior.pan());} 896 if ( dataObj._plot.enable_zoom) {this.event_panel.event("mousewheel",pv.Behavior.zoom());} 897 898 var tick_padding = 0; 899 900 if (dataObj.ticks._data_array != undefined) { 901 this._add_ticks(outerRadius); 902 tick_padding = dataObj.ticks.outer_padding + dataObj.ticks.height; 903 } 904 this.wedge_layer=[]; 905 if (dataObj._wedge != undefined) { 906 for(var i = 0; i < dataObj._wedge.length; i++){ 907 var wedge_outerRadius = 908 outerRadius - 909 (pv.sum(dataObj._wedge.slice(0,i), function(a) { return a._plot_height;}) + pv.sum(dataObj._wedge.slice(0,i), function(a) { return a._outer_padding;})) - 910 (tick_padding); 911 this._add_wedge(i,wedge_outerRadius); 912 } 913 } 914 915 var wedge_width = 0; 916 if (dataObj._wedge === undefined && dataObj._wedge.length > 0) { 917 wedge_width = dataObj._wedge[0]._outer_padding; 918 } 919 920 var label_wedge = this.event_panel.add(pv.Wedge) 921 .data(dataObj._chrom.keys) 922 .left(w/2) 923 .top(h/2) 924 .innerRadius(outerRadius-wedge_width) 925 .outerRadius(outerRadius) 926 .angle(function(d) { return dataObj.normalizedLength[d] * 2 * Math.PI;} ) 927 .startAngle(function(d) { return dataObj.startAngle_map[d]; } ) 928 .fillStyle(null) 929 .strokeStyle(null) 930 .lineWidth(0); 931 932 switch ( dataObj._chrom.label_layout_style) { 933 case ('clock'): 934 var dot = label_wedge.anchor("outer").add(pv.Dot) //chromosome labels 935 .fillStyle('rgba(140,140,140,0.7)') 936 .strokeStyle('rgba(255,255,255,0.9)') 937 .lineWidth(1) 938 .radius(14); 939 dot.anchor("center").add(pv.Label) 940 .textAlign("center") 941 .font(dataObj._chrom.label_font_style) 942 .textAngle(0); 943 if (dataObj._chrom.listener != null) { 944 dot.events('all') 945 .cursor('pointer') 946 .event('click',function(key){dataObj._chrom.listener(this.data());}); 947 } 948 break; 949 case ('default'): 950 default: 951 label_wedge.anchor("center").add(pv.Label) 952 .textAlign("center") 953 .font(dataObj._chrom.label_font_style); 954 if (dataObj._chrom.listener != null) { 955 label_wedge.events('all') 956 .cursor('pointer') 957 .event('click',function(key){dataObj._chrom.listener(this.data());}); 958 } 959 break; 960 } 961 962 var plot_radius = outerRadius - 963 (pv.sum(dataObj._wedge, function(a) { return a._plot_height;}) + pv.sum(dataObj._wedge, function(a) { return a._outer_padding;}) + 964 tick_padding); 965 966 if( dataObj._chrom.radial_grid_line_width != null && 967 dataObj._chrom.radial_grid_line_width > 0) { 968 var radial_lines = this.event_panel.add(pv.Wedge) 969 .data(dataObj._chrom.keys) 970 .left(w/2) 971 .top(h/2) 972 .innerRadius(plot_radius) 973 .outerRadius(outerRadius) 974 .angle(0) 975 .startAngle(function(d) { return dataObj.startAngle_map[d]; } ) 976 .fillStyle(null) 977 .strokeStyle('#333') 978 .lineWidth(dataObj._chrom.radial_grid_line_width); 979 } 980 981 if (dataObj._network.data != undefined) { 982 dataObj._network.radius = 983 plot_radius - (dataObj._network._outer_padding); 984 this._add_network(); 985 } 986 if (dataObj._plot.show_legend) { 987 this._add_legend(); 988 } 989 this.vis.render(); 990 }; 991 992 /** 993 * 994 * @class Internal data model for Circvis tool. 995 * 996 * @param data {JSON object} - Object described previously. 997 * @extends vq.models.VisData 998 */ 999 vq.models.CircVisData = function(data) { 1000 1001 vq.models.VisData.call(this,data); 1002 1003 this.setDataModel(); 1004 1005 1006 if (this.getDataType() == 'vq.models.CircVisData') { 1007 this._build_data(this.getContents()) 1008 } else { 1009 console.warn('Unrecognized JSON object. Expected vq.models.CircVisData object.'); 1010 } 1011 }; 1012 1013 1014 vq.models.CircVisData.prototype = pv.extend(vq.models.VisData); 1015 1016 vq.models.CircVisData.prototype.setDataModel = function() { 1017 this._dataModel = [ 1018 {label: 'width', id: 'PLOT.width', defaultValue: 400}, 1019 {label: 'height', id: 'PLOT.height', defaultValue: 400}, 1020 {label : 'container', id:'PLOT.container', optional : true}, 1021 {label: 'vertical_padding', id: 'PLOT.vertical_padding', defaultValue: 0}, 1022 {label: 'horizontal_padding', id: 'PLOT.horizontal_padding', defaultValue: 0}, 1023 {label : '_chrom.keys', id: 'GENOME.DATA.key_order', defaultValue : ["1","2","3","4","5","6","7","8","9","10", 1024 "11","12","13","14","15","16","17","18","19","20","21","22","X","Y"] }, 1025 {label : '_chrom.length', id: 'GENOME.DATA.key_length', defaultValue : [] }, 1026 {label : '_chrom.reverse_list', id: 'GENOME.OPTIONS.key_reverse_list', optional : true }, 1027 {label : '_chrom.label_layout_style', id: 'GENOME.OPTIONS.label_layout_style', defaultValue : 'default' }, 1028 {label : '_chrom.label_font_style', id: 'GENOME.OPTIONS.label_font_style', cast: String, defaultValue : "16px helvetica, monospaced" }, 1029 {label : '_chrom.radial_grid_line_width', id: 'GENOME.OPTIONS.radial_grid_line_width', cast : Number, defaultValue : null }, 1030 {label : '_chrom.listener', id: 'GENOME.OPTIONS.listener', cast: Function, defaultValue : function() {return null;}}, 1031 {label : '_plot.enable_pan', id: 'PLOT.enable_pan', cast: Boolean, defaultValue : false }, 1032 {label : '_plot.enable_zoom', id: 'PLOT.enable_zoom', cast: Boolean, defaultValue : false }, 1033 {label : '_plot.show_legend', id: 'PLOT.show_legend', cast: Boolean, defaultValue : false }, 1034 {label : '_plot.legend_corner', id: 'PLOT.legend_corner', cast: String, defaultValue : 'ne' }, 1035 {label : '_plot.legend_radius', id: 'PLOT.legend_radius', cast: Number, defaultValue : 25 }, 1036 {label : '_plot.legend_show_rings', id: 'PLOT.legend_show_rings', cast: Boolean, defaultValue : true }, 1037 {label : '_plot.rotate_degrees', id: 'PLOT.rotate_degrees', cast: Number, defaultValue : 0 }, 1038 {label : '_plot.tooltip_timeout', id: 'PLOT.tooltip_timeout', cast: Number, defaultValue : 200 }, 1039 {label : '_network.data', id: 'NETWORK.DATA.data_array', optional : true }, 1040 //{label : '_network.radius', id: 'NETWORK.OPTIONS.network_radius', cast : Number, defaultValue : 100 }, 1041 {label : '_network._outer_padding', id: 'NETWORK.OPTIONS.outer_padding', optional : true }, 1042 {label : '_network.node_listener', id: 'NETWORK.OPTIONS.node_listener', cast: Function, defaultValue : function() {return null;} }, 1043 {label : '_network.link_listener', id: 'NETWORK.OPTIONS.link_listener', cast: Function, defaultValue : function() {return null;} }, 1044 {label : '_network.link_tooltipItems', id: 'NETWORK.OPTIONS.link_tooltip_items', 1045 defaultValue : { 'Node 1 Chr' : 'sourceNode.chr', 'Node 1 Start' : 'sourceNode.start', 'Node1 End' : 'sourceNode.end', 1046 'Node 2 Chr' : 'targetNode.chr', 'Node 2 Start' : 'targetNode.start', 'Node 2 End' : 'targetNode.end'} }, 1047 {label : '_network.link_tooltipLinks', id: 'NETWORK.OPTIONS.link_tooltip_links', defaultValue : {} }, 1048 {label : '_network.link_line_width', id: 'NETWORK.OPTIONS.link_line_width', cast : vq.utils.VisUtils.wrapProperty, 1049 defaultValue : function(node,link) { return 1; }}, 1050 {label : '_network.link_alpha', id: 'NETWORK.OPTIONS.link_alpha', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function() {return 0.7;} }, 1051 {label : '_network.link_strokeStyle', id: 'NETWORK.OPTIONS.link_stroke_style', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function() { return 'red';} }, 1052 {label : '_network.node_fillStyle', id: 'NETWORK.OPTIONS.node_fill_style', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function() { return 'blue';} }, 1053 {label : '_network.node_strokeStyle', id: 'NETWORK.OPTIONS.node_stroke_style', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function() { return 'blue';} }, 1054 {label : '_network.node_key', id: 'NETWORK.OPTIONS.node_key', cast : Function, defaultValue : function(node) { return node['chr'];} }, 1055 {label : '_network.node_highlightMode', id: 'NETWORK.OPTIONS.node_highlight_mode', cast : String, defaultValue : 'brighten' }, 1056 {label : '_network.node_tooltipFormat', id: 'NETWORK.OPTIONS.node_tooltipFormat', cast : vq.utils.VisUtils.wrapProperty, defaultValue : vq.utils.VisUtils.network_node_title }, 1057 {label : '_network.node_tooltipItems', id: 'NETWORK.OPTIONS.node_tooltip_items', defaultValue : { Chr : 'chr', Start : 'start', End : 'end'} }, 1058 {label : '_network.node_tooltipLinks', id: 'NETWORK.OPTIONS.node_tooltip_links', defaultValue : {} }, 1059 {label : '_network.max_node_linkDegree', id: 'NETWORK.OPTIONS.max_node_linkdegree', cast : Number, defaultValue : 9999 }, 1060 {label : '_network.min_node_linkDegree', id: 'NETWORK.OPTIONS.min_node_linkdegree', cast : Number, defaultValue : 0 }, 1061 {label : '_network.node_overlap_distance', id: 'NETWORK.OPTIONS.node_overlap_distance', cast : Number, defaultValue : 12000000.0}, 1062 {label : '_network.tile_nodes', id: 'NETWORK.OPTIONS.tile_nodes', cast : Boolean, defaultValue : false }, 1063 {label : 'ticks.tooltipItems', id: 'TICKS.OPTIONS.tooltip_items', defaultValue : { Chr : 'chr', Start : 'start', End : 'end', Label:'value'} }, 1064 {label : 'ticks.tooltipLinks', id: 'TICKS.OPTIONS.tooltip_links', defaultValue : {} }, 1065 {label : 'ticks.label_map', id: 'TICKS.OPTIONS.label_map', defaultValue:[{key:'',label:''}]}, 1066 1067 {label : 'ticks.label_key', id: 'TICKS.OPTIONS.label_key', defaultValue:'value',cast: String}, 1068 {label : 'ticks._data_array', id: 'TICKS.DATA.data_array', optional : true }, 1069 {label : 'ticks.height', id: 'TICKS.OPTIONS.height', cast : Number, defaultValue: 60 }, 1070 {label : 'ticks.wedge_width', id: 'TICKS.OPTIONS.wedge_width', cast : Number, defaultValue: 0.5 }, 1071 {label : 'ticks.wedge_height', id: 'TICKS.OPTIONS.wedge_height', cast : Number, defaultValue: 10 }, 1072 {label : 'ticks.outer_padding', id: 'TICKS.OPTIONS.outer_padding', cast : Number, defaultValue: 0 }, 1073 {label : 'ticks.listener', id: 'TICKS.OPTIONS.listener', cast : Function, defaultValue : function() {return null;} }, 1074 {label : 'ticks.display_legend', id: 'TICKS.OPTIONS.display_legend', cast : Boolean, defaultValue : true }, 1075 {label : 'ticks.legend_corner', id: 'TICKS.OPTIONS.legend_corner', cast : String, defaultValue : 'nw' }, 1076 {label : 'ticks.tile_ticks', id: 'TICKS.OPTIONS.tile_ticks', cast : Boolean, defaultValue: true }, 1077 {label : 'ticks.overlap_distance', id: 'TICKS.OPTIONS.overlap_distance', cast : Number, optional: true}, 1078 {label : 'ticks.fill_style', id: 'TICKS.OPTIONS.fill_style', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function() { return pv.color('red');}}, 1079 {label : 'ticks.stroke_style', id: 'TICKS.OPTIONS.stroke_style', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function() { return pv.color('white');}}, 1080 {label : '_wedge' , id:'WEDGE', optional : true} 1081 ]; 1082 }; 1083 1084 vq.models.CircVisData.prototype._build_data = function(data_struct) { 1085 var data = data_struct; 1086 1087 this._processData(data); 1088 1089 if (this._wedge) { 1090 this._wedge = this._wedge.map(function(b) { 1091 return new vq.models.CircVisData.WedgeData(b); 1092 }); 1093 } 1094 1095 this._setupData(); 1096 }; 1097 1098 1099 vq.models.CircVisData.prototype._setupData = function() { 1100 var chrom_keys_order,chrom_length_map,chrom_length_array=[],cnv_map, startAngle={}, 1101 cnv_array, cnv_height= [], startAngle_map={},normalizedLength={}, 1102 deviation =[],median =[], theta={}, totalChromLength; 1103 this.normalizedLength,this.theta=[],this.startAngle_map; 1104 1105 var that = this; 1106 1107 if (this._chrom.keys == [] || this._chrom.length == []) { 1108 console.warn('Chromosome/Ideogram information has not been detected. Please verify that keys and length/key mappings have been ' + 1109 'passed into the GENOME.DATA object.'); 1110 return; 1111 } 1112 1113 var chrom_keys_array = this._chrom.keys; //array in pre-sorted order 1114 chrom_keys_order= pv.numerate(chrom_keys_array); 1115 1116 chrom_length_array = this._chrom.length.filter(function(d) {return chrom_keys_order[d['chr_name']] != null;}); 1117 chrom_length_array.sort(function(c,d) {return chrom_keys_order[c['chr_name']] - chrom_keys_order[d['chr_name']] > 0;}); //sort by given order 1118 totalChromLength = pv.sum(chrom_length_array, function(d) { return d['chr_length'];} ); 1119 1120 chrom_length_map = pv.nest( chrom_length_array ) 1121 .key( function(d) { return d['chr_name'].toUpperCase();} ) 1122 .sortKeys(function(c,d) {return chrom_keys_order[c['chr_name']] - chrom_keys_order[d['chr_name']] > 0;}) //sort by given order 1123 .map(); 1124 1125 normalizedLength = pv.dict(chrom_keys_array, function(d) { return chrom_length_map[d.toUpperCase()][0]['chr_length'] / totalChromLength;}); 1126 1127 this.normalizedLength = normalizedLength; 1128 1129 //for each index of chrom_keys ( pre-sorted) 1130 // sum all lengths from 1st index to last index of chrom_length (sorted to chrom_length) 1131 chrom_keys_array.forEach( function(d) { 1132 startAngle[d] = pv.sum(chrom_keys_array.slice(0,(chrom_keys_order[d])), 1133 function() { 1134 return (normalizedLength[chrom_keys_array[this.index]] * 2 * Math.PI);} ); 1135 1136 theta[d] = pv.Scale.linear( 0 , chrom_length_map[d.toUpperCase()][0]['chr_length']) 1137 .range(0,2 * Math.PI * normalizedLength[d]); 1138 1139 if ( that._chrom.reverse_list != undefined && 1140 that._chrom.reverse_list.filter(function(c){return c == d;}).length > 0){ //defined as reversed! 1141 theta[d] = pv.Scale.linear( 0 , chrom_length_map[d.toUpperCase()][0]['chr_length']) 1142 .range(2 * Math.PI * normalizedLength[d],0); 1143 1144 } else { 1145 theta[d] = pv.Scale.linear( 0 , chrom_length_map[d.toUpperCase()][0]['chr_length']) 1146 .range(0,2 * Math.PI * normalizedLength[d]); 1147 1148 } 1149 }); 1150 1151 var rotation = (this._plot.rotate_degrees) * Math.PI /180; 1152 1153 startAngle_map = pv.dict(chrom_keys_array,(function(d) {return startAngle[d] - (Math.PI / 2) + rotation; } )); 1154 this.startAngle_map = startAngle_map; 1155 this.theta = theta; 1156 1157 if (this._wedge != undefined) { 1158 this._wedge.forEach(function(wedge,index) { 1159 1160 if (wedge._plot_type == 'tile' || wedge._plot_type == 'glyph') { 1161 var max_tile_level = wedge._tile_show_all_tiles ? 1162 Math.floor((wedge._plot_height - (wedge._radius() * 4)) / (wedge._tile_height + wedge._tile_padding)) : 1163 undefined; 1164 wedge._data = (wedge._plot_type =='tile' ? vq.utils.VisUtils.layoutChrTiles(wedge._data,wedge._tile_overlap_distance,max_tile_level) : 1165 vq.utils.VisUtils.layoutChrTicks(wedge._data,wedge._tile_overlap_distance,max_tile_level)); 1166 } 1167 1168 cnv_map = pv.nest(wedge._data) 1169 .key(function(d) { return d.chr; } ) 1170 .map(); 1171 1172 wedge._chr_map = []; 1173 wedge._chr_map = pv.dict(that._chrom.keys, function(d) 1174 { return cnv_map[d] === undefined ? [] : cnv_map[d]; }); 1175 1176 var value_label = wedge._value_key; 1177 deviation = pv.deviation(wedge._data, function(d) { return d[value_label];}); 1178 median = pv.median(wedge._data, function(d) { return d[value_label];}); 1179 1180 wedge._min_plotValue = (wedge._min_plotValue === undefined) ? parseFloat(((-1 * deviation) + median).toFixed(2)) : wedge._min_plotValue; 1181 wedge._max_plotValue = (wedge._max_plotValue === undefined) ? parseFloat((deviation + median).toFixed(2)) : wedge._max_plotValue; 1182 1183 delete wedge._data; 1184 }); //foreach 1185 } 1186 //------------------- NETWORK DATA 1187 var nodes = pv.dict(this._chrom.keys, function() { return {}; }); 1188 var node_array=[]; 1189 var links_array = []; 1190 var length; 1191 var index1,index2; 1192 if (this._network != undefined && this._network.data != undefined) { 1193 this._network.data.forEach(function(d) { 1194 index1 = null; 1195 index2 = null; 1196 if (nodes[d.node1.chr] != undefined){ 1197 if (nodes[d.node1.chr][d.node1.start] === undefined ){ 1198 nodes[d.node1.chr][d.node1.start] = {}; 1199 if (nodes[d.node1.chr][d.node1.start][d.node1.end] === undefined ) { 1200 var temp_node = d.node1; 1201 temp_node.nodeName = d.node1.chr; 1202 length = node_array.push(temp_node); 1203 index1 = length - 1; 1204 nodes[d.node1.chr][d.node1.start][d.node1.end] = index1; 1205 } else { 1206 index1 = nodes[d.node1.chr][d.node1.start][d.node1.end]; 1207 } 1208 } else { 1209 index1 = nodes[d.node1.chr][d.node1.start][d.node1.end]; 1210 } 1211 } 1212 if (nodes[d.node2.chr] != undefined) { 1213 if (nodes[d.node2.chr][d.node2.start] === undefined ) { 1214 nodes[d.node2.chr][d.node2.start] = {}; 1215 if (nodes[d.node2.chr][d.node2.start][d.node2.end] === undefined ) { 1216 var temp_node = d.node2; 1217 temp_node.nodeName = d.node2.chr; 1218 length = node_array.push(temp_node); 1219 index2 = length - 1; 1220 nodes[d.node2.chr][d.node2.start][d.node2.end] = index2; 1221 } else { 1222 index2 = nodes[d.node2.chr][d.node2.start][d.node2.end]; 1223 } 1224 } else { 1225 index2 = nodes[d.node2.chr][d.node2.start][d.node2.end]; 1226 } 1227 } 1228 1229 if (index1 != null && index2 !=null) { 1230 //copy out useful properties 1231 var node = {source : index1, target : index2} ; 1232 for (var p in d) { 1233 if (p != 'node1' && p!= 'node2') { 1234 node[p] = d[p]; 1235 } 1236 } 1237 links_array.push(node); 1238 } 1239 }); 1240 this._network.nodes_array = this._network.tile_nodes ? vq.utils.VisUtils.layoutChrTiles(node_array,that._network.node_overlap_distance) : node_array; 1241 this._network.links_array = links_array; 1242 this._network.data = 'loaded'; 1243 nodes = []; 1244 node_array = []; 1245 links_array = []; 1246 } 1247 1248 if (this.ticks != undefined && this.ticks._data_array != undefined && this.ticks._data_array != null) { 1249 if (that.ticks.overlap_distance === undefined) { 1250 var overlap_ratio = 7000000.0 / 3080419480; 1251 that.ticks.overlap_distance = overlap_ratio * totalChromLength; 1252 } 1253 var tick_array = that.ticks.tile_ticks ? vq.utils.VisUtils.layoutChrTicks(that.ticks._data_array,that.ticks.overlap_distance) : 1254 that.ticks._data_array; 1255 1256 var ticks_map = pv.nest(tick_array) 1257 .key(function(d) {return d.chr;}) 1258 .map(); 1259 1260 this.ticks.data_map = pv.dict(that._chrom.keys, function(d) 1261 { return ticks_map[d] === undefined ? [] : ticks_map[d]; }); 1262 this.ticks._data_array = []; 1263 delete tick_array; 1264 ticks_map = []; 1265 } 1266 this.setDataReady(true); 1267 }; 1268 1269 1270 /** 1271 * 1272 * @class Internal data model for ring plots. 1273 * 1274 * @param data {JSON Object} - Configures a single ring plot. 1275 * @extends vq.models.VisData 1276 */ 1277 vq.models.CircVisData.WedgeData = function(data) { 1278 1279 vq.models.VisData.call(this,{CONTENTS:data}); 1280 1281 this.setDataModel(); 1282 this._build_data(this.getContents()) 1283 1284 }; 1285 1286 vq.models.CircVisData.WedgeData.prototype = pv.extend(vq.models.VisData); 1287 1288 1289 vq.models.CircVisData.WedgeData.prototype.setDataModel = function() { 1290 this._dataModel = [ 1291 {label : '_data', id: 'DATA.data_array', defaultValue : [ {"chr": "1", "end": 12784268, "start": 644269, 1292 "value": -0.058664}]}, 1293 {label : '_value_key', id: 'DATA.value_key', defaultValue : 'value',cast: String }, 1294 {label : 'listener', id: 'OPTIONS.listener', defaultValue : function(a,b) {} }, 1295 {label : '_plot_type', id: 'PLOT.type', defaultValue : 'histogram' }, 1296 {label : '_plot_height', id: 'PLOT.height', cast: Number, defaultValue : 100 }, 1297 {label : '_fillStyle', id: 'OPTIONS.fill_style', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function(d) { return pv.color('red');} }, 1298 {label : '_strokeStyle', id: 'OPTIONS.stroke_style', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function(d) {return pv.color('red');} }, 1299 {label : '_shape', id: 'OPTIONS.shape', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function(d) {return 'circle';} }, 1300 {label : '_radius', id: 'OPTIONS.radius', cast : vq.utils.VisUtils.wrapProperty, defaultValue : function(d) {return 2;} }, 1301 {label : '_outer_padding', id: 'OPTIONS.outer_padding', cast : Number, defaultValue : 1 }, 1302 {label : '_min_plotValue', id: 'OPTIONS.min_value', cast : Number , optional : true }, 1303 {label : '_max_plotValue', id: 'OPTIONS.max_value', cast : Number , optional : true }, 1304 {label : '_base_plotValue', id: 'OPTIONS.base_value', cast: Number, optional : true }, 1305 {label : '_legend_label', id: 'OPTIONS.legend_label', cast: String, defaultValue : '' }, 1306 {label : '_legend_desc', id: 'OPTIONS.legend_description', cast: String, defaultValue : '' }, 1307 {label : '_draw_axes', id: 'OPTIONS.draw_axes', cast: Boolean, defaultValue : true }, 1308 {label : '_tooltipFormat', id: 'OPTIONS.tooltipFormat', cast :vq.utils.VisUtils.wrapProperty, 1309 defaultValue : function(c,d) { return "Chr " + d + "\nStart: " + c.start + "\nEnd: " + c.end;} }, 1310 {label : '_tooltipItems', id: 'OPTIONS.tooltip_items', defaultValue : {Chr:'chr',Start:'start',End:'end',Value:'value'} }, 1311 {label : '_tooltipLinks', id: 'OPTIONS.tooltip_links', defaultValue : {} }, 1312 {label : '_tile_padding', id: 'OPTIONS.tile_padding', cast: Number, defaultValue : 5 }, 1313 {label : '_tile_overlap_distance', id: 'OPTIONS.tile_overlap_distance', cast: Number, defaultValue : 0.1 }, 1314 {label : '_tile_height', id: 'OPTIONS.tile_height', cast: Number, defaultValue : 5 }, 1315 {label : '_tile_show_all_tiles', id: 'OPTIONS.tile_show_all_tiles', cast: Boolean, defaultValue : false } 1316 ]; 1317 }; 1318 1319 vq.models.CircVisData.WedgeData.prototype._build_data = function(data_struct) { 1320 this._processData(data_struct) 1321 }; 1322