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