Devel-SizeMe

 view release on metacpan or  search on metacpan

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

            var canvasConfig = this.config;
            if(canvasConfig.useCanvas) {
              this.canvas = canvasConfig.useCanvas;
              this.config.labelContainer = this.canvas.id + '-label';
            } else {
              if(canvasConfig.background) {
                canvasConfig.background = $.merge({
                  type: 'Circles'
                }, canvasConfig.background);
              }
              this.canvas = new Canvas(this, canvasConfig);
              this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
            }

            this.graphOptions = {
                'klass': Complex
            };
            this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
            this.labels = new $ST.Label[canvasConfig.Label.type](this);
            this.fx = new $ST.Plot(this, $ST);
            this.op = new $ST.Op(this);
            this.group = new $ST.Group(this);
            this.geom = new $ST.Geom(this);
            this.clickedNode=  null;
            // initialize extras
            this.initializeExtras();
        },
    
        /*
         Method: plot
        
         Plots the <ST>. This is a shortcut to *fx.plot*.

        */  
        plot: function() { this.fx.plot(this.controller); },
    
      
        /*
         Method: switchPosition
        
         Switches the tree orientation.

         Parameters:

        pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
        method - (string) Set this to "animate" if you want to animate the tree when switching its position. You can also set this parameter to "replot" to just replot the subtree.
        onComplete - (optional|object) This callback is called once the "switching" animation is complete.

         Example:

         (start code js)
           st.switchPosition("right", "animate", {
            onComplete: function() {
              alert('completed!');
            } 
           });
         (end code)
        */  
        switchPosition: function(pos, method, onComplete) {
          var Geom = this.geom, Plot = this.fx, that = this;
          if(!Plot.busy) {
              Plot.busy = true;
              this.contract({
                  onComplete: function() {
                      Geom.switchOrientation(pos);
                      that.compute('end', false);
                      Plot.busy = false;
                      if(method == 'animate') {
                    	  that.onClick(that.clickedNode.id, onComplete);  
                      } else if(method == 'replot') {
                    	  that.select(that.clickedNode.id, onComplete);
                      }
                  }
              }, pos);
          }
        },

        /*
        Method: switchAlignment
       
        Switches the tree alignment.

        Parameters:

       align - (string) The new tree alignment. Possible values are "left", "center" and "right".
       method - (string) Set this to "animate" if you want to animate the tree after aligning its position. You can also set this parameter to "replot" to just replot the subtree.
       onComplete - (optional|object) This callback is called once the "switching" animation is complete.

        Example:

        (start code js)
          st.switchAlignment("right", "animate", {
           onComplete: function() {
             alert('completed!');
           } 
          });
        (end code)
       */  
       switchAlignment: function(align, method, onComplete) {
        this.config.align = align;
        if(method == 'animate') {
        	this.select(this.clickedNode.id, onComplete);
        } else if(method == 'replot') {
        	this.onClick(this.clickedNode.id, onComplete);	
        }
       },

       /*
        Method: addNodeInPath
       
        Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
        

        Parameters:

       id - (string) A <Graph.Node> id.

        Example:

        (start code js)
          st.addNodeInPath("nodeId");
        (end code)
       */  
       addNodeInPath: function(id) {
           nodesInPath.push(id);
           this.select((this.clickedNode && this.clickedNode.id) || this.root);
       },       

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

        },
      
         move: function(node, onComplete) {
            this.compute('end', false);
            var move = onComplete.Move, offset = {
                'x': move.offsetX,
                'y': move.offsetY 
            };
            if(move.enable) {
                this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
            }
            this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
         },
      
        expand: function (node, onComplete) {
            var nodeArray = getNodesToShow.call(this, node);
            this.group.expand(nodeArray, $.merge(this.controller, onComplete));
        },
    
        selectPath: function(node) {
          var that = this;
          this.graph.eachNode(function(n) { n.selected = false; }); 
          function path(node) {
              if(node == null || node.selected) return;
              node.selected = true;
              $.each(that.group.getSiblings([node])[node.id], 
              function(n) { 
                   n.exist = true; 
                   n.drawn = true; 
              });    
              var parents = node.getParents();
              parents = (parents.length > 0)? parents[0] : null;
              path(parents);
          };
          for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
              path(this.graph.getNode(ns[i]));
          }
        },
      
        /*
        Method: setRoot
     
         Switches the current root node. Changes the topology of the Tree.
     
        Parameters:
           id - (string) The id of the node to be set as root.
           method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
           onComplete - (optional|object) An action to perform after the animation (if any).
 
        Example:

        (start code js)
          st.setRoot('nodeId', 'animate', {
             onComplete: function() {
               alert('complete!');
             }
          });
        (end code)
     */
     setRoot: function(id, method, onComplete) {
        	if(this.busy) return;
        	this.busy = true;
          var that = this, canvas = this.canvas;
        	var rootNode = this.graph.getNode(this.root);
        	var clickedNode = this.graph.getNode(id);
        	function $setRoot() {
            	if(this.config.multitree && clickedNode.data.$orn) {
            		var orn = clickedNode.data.$orn;
            		var opp = {
            				'left': 'right',
            				'right': 'left',
            				'top': 'bottom',
            				'bottom': 'top'
            		}[orn];
            		rootNode.data.$orn = opp;
            		(function tag(rootNode) {
                		rootNode.eachSubnode(function(n) {
                			if(n.id != id) {
                				n.data.$orn = opp;
                				tag(n);
                			}
                		});
            		})(rootNode);
            		delete clickedNode.data.$orn;
            	}
            	this.root = id;
            	this.clickedNode = clickedNode;
            	this.graph.computeLevels(this.root, 0, "ignore");
            	this.geom.setRightLevelToShow(clickedNode, canvas, {
            	  execHide: false,
            	  onShow: function(node) {
            	    if(!node.drawn) {
                    node.drawn = true;
                    node.setData('alpha', 1, 'end');
                    node.setData('alpha', 0);
                    node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
            	    }
            	  }
            	});
              this.compute('end');
              this.busy = true;
              this.fx.animate({
                modes: ['linear', 'node-property:alpha'],
                onComplete: function() {
                  that.busy = false;
                  that.onClick(id, {
                    onComplete: function() {
                      onComplete && onComplete.onComplete();
                    }
                  });
                }
              });
        	}

        	// delete previous orientations (if any)
        	delete rootNode.data.$orns;

        	if(method == 'animate') {
        	  $setRoot.call(this);
        	  that.selectPath(clickedNode);
        	} else if(method == 'replot') {
        		$setRoot.call(this);
        		this.select(this.root);
        	}
     },

     /*
           Method: addSubtree
        
            Adds a subtree.
        
           Parameters:
              subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
              method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
              onComplete - (optional|object) An action to perform after the animation (if any).
    
           Example:

           (start code js)
             st.addSubtree(json, 'animate', {
                onComplete: function() {
                  alert('complete!');
                }
             });
           (end code)
        */
        addSubtree: function(subtree, method, onComplete) {
            if(method == 'replot') {
                this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
            } else if (method == 'animate') {
                this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
            }
        },
    
        /*
           Method: removeSubtree
        
            Removes a subtree.
        
           Parameters:
              id - (string) The _id_ of the subtree to be removed.
              removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
              method - (string) Set this to "animate" if you want to animate the tree after removing the subtree. You can also set this parameter to "replot" to just replot the subtree.
              onComplete - (optional|object) An action to perform after the animation (if any).

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

                    that.graph.eachNode(function(n) { 
                        var pos = n.pos.getc(true);
                        n.startPos.setc(pos.x, pos.y);
                        n.endPos.setc(pos.x, pos.y);
                        n.visited = false; 
                    });
                    var offset = { x: complete.offsetX, y: complete.offsetY };
                    that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
                    group.show(getNodesToShow.call(that));              
                    that.plot();
                    complete.onAfterCompute(that.clickedNode);
                    complete.onComplete();
                }
            });     
        },
    
      /*
         Method: onClick
    
        Animates the <ST> to center the node specified by *id*.
            
        Parameters:
        
        id - (string) A node id.
        options - (optional|object) A group of options and callbacks described below.
        onComplete - (object) An object callback called when the animation finishes.
        Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.

        Example:

        (start code js)
          st.onClick('mynodeid', {
	          Move: {
	          	enable: true,
	            offsetX: 30,
	            offsetY: 5
	          },
	          onComplete: function() {
	              alert('yay!');
	          }
          });
        (end code)
    
        */    
      onClick: function (id, options) {
        var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
        var innerController = {
            Move: {
        	    enable: true,
              offsetX: config.offsetX || 0,
              offsetY: config.offsetY || 0  
            },
            setRightLevelToShowConfig: false,
            onBeforeRequest: $.empty,
            onBeforeContract: $.empty,
            onBeforeMove: $.empty,
            onBeforeExpand: $.empty
        };
        var complete = $.merge(this.controller, innerController, options);
        
        if(!this.busy) {
            this.busy = true;
            var node = this.graph.getNode(id);
            this.selectPath(node, this.clickedNode);
           	this.clickedNode = node;
            complete.onBeforeCompute(node);
            complete.onBeforeRequest(node);
            this.requestNodes(node, {
                onComplete: function() {
                    complete.onBeforeContract(node);
                    that.contract({
                        onComplete: function() {
                            Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
                            complete.onBeforeMove(node);
                            that.move(node, {
                                Move: complete.Move,
                                onComplete: function() {
                                    complete.onBeforeExpand(node);
                                    that.expand(node, {
                                        onComplete: function() {
                                            that.busy = false;
                                            complete.onAfterCompute(id);
                                            complete.onComplete();
                                        }
                                    }); // expand
                                }
                            }); // move
                        }
                    });// contract
                }
            });// request
        }
      }
    });

})();

$jit.ST.$extend = true;

/*
   Class: ST.Op
    
   Custom extension of <Graph.Op>.

   Extends:

   All <Graph.Op> methods
   
   See also:
   
   <Graph.Op>

*/
$jit.ST.Op = new Class({

  Implements: Graph.Op
    
});

/*
    
     Performs operations on group of nodes.

*/
$jit.ST.Group = new Class({
    
    initialize: function(viz) {
        this.viz = viz;
        this.canvas = viz.canvas;
        this.config = viz.config;
        this.animation = new Animation;
        this.nodes = null;
    },
    
    /*
    
       Calls the request method on the controller to request a subtree for each node. 
    */
    requestNodes: function(nodes, controller) {
        var counter = 0, len = nodes.length, nodeSelected = {};
        var complete = function() { controller.onComplete(); };

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

          ctx.textBaseline = 'middle';
          var aggValue = aggregates(node.name, valLeft, valRight, node, valAcum);
          if(aggValue !== false) {
            ctx.fillText(aggValue !== true? aggValue : valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
          }
          if(showLabels(node.name, valLeft, valRight, node)) {
            ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
          }
          ctx.restore();
        }
      }
    },
    'contains': function(node, mpos) {
      var pos = node.pos.getc(true), 
          width = node.getData('width'),
          height = node.getData('height'),
          algnPos = this.getAlignedPos(pos, width, height),
          x = algnPos.x, y = algnPos.y,
          dimArray = node.getData('dimArray'),
          rx = mpos.x - x;
      //bounding box check
      if(mpos.x < x || mpos.x > x + width
        || mpos.y > y || mpos.y < y - height) {
        return false;
      }
      //deep check
      for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
        var dimi = dimArray[i];
        lAcum -= dimi[0];
        rAcum -= dimi[1];
        var intersec = lAcum + (rAcum - lAcum) * rx / width;
        if(mpos.y >= intersec) {
          var index = +(rx > width/2);
          return {
            'name': node.getData('stringArray')[i],
            'color': node.getData('colorArray')[i],
            'value': node.getData('valueArray')[i][index],
            'index': index
          };
        }
      }
      return false;
    }
  }
});

/*
  Class: AreaChart
  
  A visualization that displays stacked area charts.
  
  Constructor Options:
  
  See <Options.AreaChart>.

*/
$jit.AreaChart = new Class({
  st: null,
  colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
  selected: {},
  busy: false,
  
  initialize: function(opt) {
    this.controller = this.config = 
      $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
        Label: { type: 'Native' }
      }, opt);
    //set functions for showLabels and showAggregates
    var showLabels = this.config.showLabels,
        typeLabels = $.type(showLabels),
        showAggregates = this.config.showAggregates,
        typeAggregates = $.type(showAggregates);
    this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
    this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
    
    this.initializeViz();
  },
  
  initializeViz: function() {
    var config = this.config,
        that = this,
        nodeType = config.type.split(":")[0],
        nodeLabels = {};

    var delegate = new $jit.ST({
      injectInto: config.injectInto,
      width: config.width,
      height: config.height,
      orientation: "bottom",
      levelDistance: 0,
      siblingOffset: 0,
      subtreeOffset: 0,
      withLabels: config.Label.type != 'Native',
      useCanvas: config.useCanvas,
      Label: {
        type: config.Label.type
      },
      Node: {
        overridable: true,
        type: 'areachart-' + nodeType,
        align: 'left',
        width: 1,
        height: 1
      },
      Edge: {
        type: 'none'
      },
      Tips: {
        enable: config.Tips.enable,
        type: 'Native',
        force: true,
        onShow: function(tip, node, contains) {
          var elem = contains;
          config.Tips.onShow(tip, elem, node);
        }
      },
      Events: {
        enable: true,
        type: 'Native',
        onClick: function(node, eventInfo, evt) {
          if(!config.filterOnClick && !config.Events.enable) return;

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

      ch.push({
        'id': prefix + val.label,
        'name': val.label,
        'data': {
          'value': valArray,
          '$valueArray': valArray,
          '$colorArray': color,
          '$stringArray': name,
          '$next': next.label,
          '$prev': prev? prev.label:false,
          '$config': config,
          '$gradient': gradient
        },
        'children': []
      });
    }
    var root = {
      'id': prefix + '$root',
      'name': '',
      'data': {
        '$type': 'none',
        '$width': 1,
        '$height': 1
      },
      'children': ch
    };
    delegate.loadJSON(root);
    
    this.normalizeDims();
    delegate.compute();
    delegate.select(delegate.root);
    if(animate) {
      delegate.fx.animate({
        modes: ['node-property:height:dimArray'],
        duration:1500
      });
    }
  },
  
 /*
  Method: updateJSON
 
  Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
  
  Parameters:
  
  json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
  onComplete - (object) A callback object to be called when the animation transition when updating the data end.
  
  Example:
  
  (start code js)
  areaChart.updateJSON(json, {
    onComplete: function() {
      alert('update complete!');
    }
  });
  (end code)
 */  
  updateJSON: function(json, onComplete) {
    if(this.busy) return;
    this.busy = true;
    
    var delegate = this.delegate,
        graph = delegate.graph,
        labels = json.label && $.splat(json.label),
        values = json.values,
        animate = this.config.animate,
        that = this,
        hashValues = {};

    //convert the whole thing into a hash
    for (var i = 0, l = values.length; i < l; i++) {
      hashValues[values[i].label] = values[i];
    }
  
    graph.eachNode(function(n) {
      var v = hashValues[n.name],
          stringArray = n.getData('stringArray'),
          valArray = n.getData('valueArray'),
          next = n.getData('next');
      
      if (v) {
        v.values = $.splat(v.values);
        $.each(valArray, function(a, i) {
          a[0] = v.values[i];
          if(labels) stringArray[i] = labels[i];
        });
        n.setData('valueArray', valArray);
      }
     
      if(next) {
        v = hashValues[next];
        if(v) {
          $.each(valArray, function(a, i) {
            a[1] = v.values[i];
          });
        }
      }
    });
    this.normalizeDims();
    delegate.compute();
    delegate.select(delegate.root);
    if(animate) {
      delegate.fx.animate({
        modes: ['node-property:height:dimArray'],
        duration:1500,
        onComplete: function() {
          that.busy = false;
          onComplete && onComplete.onComplete();
        }
      });
    }
  },
  
/*
  Method: filter
 
  Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
  
  Parameters:
  
  filters - (array) An array of strings with the name of the stacks to be filtered.
  callback - (object) An object with an *onComplete* callback method. 
  
  Example:
  
  (start code js)
  areaChart.filter(['label A', 'label C'], {
      onComplete: function() {
          console.log('done!');
      }
  });
  (end code)
  
  See also:
  
  <AreaChart.restore>.
 */  
  filter: function(filters, callback) {
    if(this.busy) return;
    this.busy = true;
    if(this.config.Tips.enable) this.delegate.tips.hide();
    this.select(false, false, false);
    var args = $.splat(filters);
    var rt = this.delegate.graph.getNode(this.delegate.root);
    var that = this;
    this.normalizeDims();
    rt.eachAdjacency(function(adj) {
      var n = adj.nodeTo, 
          dimArray = n.getData('dimArray', 'end'),
          stringArray = n.getData('stringArray');
      n.setData('dimArray', $.map(dimArray, function(d, i) {
        return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
      }), 'end');
    });
    this.delegate.fx.animate({
      modes: ['node-property:dimArray'],
      duration:1500,
      onComplete: function() {
        that.busy = false;
        callback && callback.onComplete();
      }
    });
  },
  
  /*
  Method: restore
 
  Sets all stacks that could have been filtered visible.
  
  Example:
  
  (start code js)
  areaChart.restore();
  (end code)
  
  See also:
  
  <AreaChart.filter>.
 */  
  restore: function(callback) {
    if(this.busy) return;
    this.busy = true;
    if(this.config.Tips.enable) this.delegate.tips.hide();
    this.select(false, false, false);
    this.normalizeDims();
    var that = this;
    this.delegate.fx.animate({
      modes: ['node-property:height:dimArray'],
      duration:1500,
      onComplete: function() {
        that.busy = false;
        callback && callback.onComplete();
      }
    });
  },
  //adds the little brown bar when hovering the node
  select: function(id, name, index) {
    if(!this.config.selectOnHover) return;
    var s = this.selected;
    if(s.id != id || s.name != name 
        || s.index != index) {
      s.id = id;
      s.name = name;
      s.index = index;
      this.delegate.graph.eachNode(function(n) {
        n.setData('border', false);
      });
      if(id) {
        var n = this.delegate.graph.getNode(id);
        n.setData('border', s);
        var link = index === 0? 'prev':'next';
        link = n.getData(link);
        if(link) {
          n = this.delegate.graph.getByName(link);
          if(n) {
            n.setData('border', {
              name: name,
              index: 1-index
            });
          }
        }
      }
      this.delegate.plot();
    }
  },
  
  /*
    Method: getLegend
   
    Returns an object containing as keys the legend names and as values hex strings with color values.
    
    Example:
    
    (start code js)
    var legend = areaChart.getLegend();
    (end code)
 */  
  getLegend: function() {
    var legend = {};
    var n;
    this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
      n = adj.nodeTo;
    });
    var colors = n.getData('colorArray'),
        len = colors.length;
    $.each(n.getData('stringArray'), function(s, i) {
      legend[s] = colors[i % len];
    });
    return legend;
  },
  

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

          config = node.getData('config'),
          rx = mpos.x - x,
          horz = config.orientation == 'horizontal',
          fixedDim = (horz? height : width) / len;
      //bounding box check
      if(horz) {
        if(mpos.x < x || mpos.x > x + width
            || mpos.y > y + height || mpos.y < y) {
            return false;
          }
      } else {
        if(mpos.x < x || mpos.x > x + width
            || mpos.y > y || mpos.y < y - height) {
            return false;
          }
      }
      //deep check
      for(var i=0, l=dimArray.length; i<l; i++) {
        var dimi = dimArray[i];
        if(horz) {
          var limit = y + fixedDim * i;
          if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
            return {
              'name': node.getData('stringArray')[i],
              'color': node.getData('colorArray')[i],
              'value': node.getData('valueArray')[i],
              'label': node.name
            };
          }
        } else {
          var limit = x + fixedDim * i;
          if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
            return {
              'name': node.getData('stringArray')[i],
              'color': node.getData('colorArray')[i],
              'value': node.getData('valueArray')[i],
              'label': node.name
            };
          }
        }
      }
      return false;
    }
  }
});

/*
  Class: BarChart
  
  A visualization that displays stacked bar charts.
  
  Constructor Options:
  
  See <Options.BarChart>.

*/
$jit.BarChart = new Class({
  st: null,
  colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
  selected: {},
  busy: false,
  
  initialize: function(opt) {
    this.controller = this.config = 
      $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
        Label: { type: 'Native' }
      }, opt);
    //set functions for showLabels and showAggregates
    var showLabels = this.config.showLabels,
        typeLabels = $.type(showLabels),
        showAggregates = this.config.showAggregates,
        typeAggregates = $.type(showAggregates);
    this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
    this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
    
    this.initializeViz();
  },
  
  initializeViz: function() {
    var config = this.config, that = this;
    var nodeType = config.type.split(":")[0],
        horz = config.orientation == 'horizontal',
        nodeLabels = {};
    
    var delegate = new $jit.ST({
      injectInto: config.injectInto,
      width: config.width,
      height: config.height,
      orientation: horz? 'left' : 'bottom',
      levelDistance: 0,
      siblingOffset: config.barsOffset,
      subtreeOffset: 0,
      withLabels: config.Label.type != 'Native',      
      useCanvas: config.useCanvas,
      Label: {
        type: config.Label.type
      },
      Node: {
        overridable: true,
        type: 'barchart-' + nodeType,
        align: 'left',
        width: 1,
        height: 1
      },
      Edge: {
        type: 'none'
      },
      Tips: {
        enable: config.Tips.enable,
        type: 'Native',
        force: true,
        onShow: function(tip, node, contains) {
          var elem = contains;
          config.Tips.onShow(tip, elem, node);
        }
      },
      Events: {
        enable: true,
        type: 'Native',
        onClick: function(node, eventInfo, evt) {
          if(!config.Events.enable) return;

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

          }
          if(config.showLabels(node.name, acum, node)) {
            labelStyle.display = '';
          } else {
            labelStyle.display = 'none';
          }
          var aggValue = config.showAggregates(node.name, acum, node);
          if(aggValue !== false) {
            aggregateStyle.display = '';
          } else {
            aggregateStyle.display = 'none';
          }
          if(config.orientation == 'horizontal') {
            aggregateStyle.textAlign = 'right';
            labelStyle.textAlign = 'left';
            labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
            aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
            domElement.style.height = wrapperStyle.height = height + 'px';
          } else {
            aggregateStyle.top = (-font - config.labelOffset) + 'px';
            labelStyle.top = (config.labelOffset + height) + 'px';
            domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
            domElement.style.height = wrapperStyle.height = height + 'px';
          }
          labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
        }
      }
    });
    
    var size = delegate.canvas.getSize(),
        margin = config.Margin;
    if(horz) {
      delegate.config.offsetX = size.width/2 - margin.left
        - (config.showLabels && (config.labelOffset + config.Label.size));    
      delegate.config.offsetY = (margin.bottom - margin.top)/2;
    } else {
      delegate.config.offsetY = -size.height/2 + margin.bottom 
        + (config.showLabels && (config.labelOffset + config.Label.size));
      delegate.config.offsetX = (margin.right - margin.left)/2;
    }
    this.delegate = delegate;
    this.canvas = this.delegate.canvas;
  },
  
  /*
    Method: loadJSON
   
    Loads JSON data into the visualization. 
    
    Parameters:
    
    json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
    
    Example:
    (start code js)
    var barChart = new $jit.BarChart(options);
    barChart.loadJSON(json);
    (end code)
 */  
  loadJSON: function(json) {
    if(this.busy) return;
    this.busy = true;
    
    var prefix = $.time(), 
        ch = [], 
        delegate = this.delegate,
        name = $.splat(json.label), 
        color = $.splat(json.color || this.colors),
        config = this.config,
        gradient = !!config.type.split(":")[1],
        animate = config.animate,
        horz = config.orientation == 'horizontal',
        that = this;
    
    for(var i=0, values=json.values, l=values.length; i<l; i++) {
      var val = values[i]
      var valArray = $.splat(values[i].values);
      var acum = 0;
      ch.push({
        'id': prefix + val.label,
        'name': val.label,
        'data': {
          'value': valArray,
          '$valueArray': valArray,
          '$colorArray': color,
          '$stringArray': name,
          '$gradient': gradient,
          '$config': config
        },
        'children': []
      });
    }
    var root = {
      'id': prefix + '$root',
      'name': '',
      'data': {
        '$type': 'none',
        '$width': 1,
        '$height': 1
      },
      'children': ch
    };
    delegate.loadJSON(root);
    
    this.normalizeDims();
    delegate.compute();
    delegate.select(delegate.root);
    if(animate) {
      if(horz) {
        delegate.fx.animate({
          modes: ['node-property:width:dimArray'],
          duration:1500,
          onComplete: function() {
            that.busy = false;
          }
        });
      } else {
        delegate.fx.animate({
          modes: ['node-property:height:dimArray'],
          duration:1500,
          onComplete: function() {
            that.busy = false;
          }
        });
      }
    } else {
      this.busy = false;
    }
  },
  
  /*
    Method: updateJSON
   
    Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
    
    Parameters:
    
    json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
    onComplete - (object) A callback object to be called when the animation transition when updating the data end.
    
    Example:
    
    (start code js)
    barChart.updateJSON(json, {
      onComplete: function() {
        alert('update complete!');
      }
    });
    (end code)
 */  
  updateJSON: function(json, onComplete) {
    if(this.busy) return;
    this.busy = true;
    this.select(false, false, false);
    var delegate = this.delegate;
    var graph = delegate.graph;
    var values = json.values;
    var animate = this.config.animate;
    var that = this;
    var horz = this.config.orientation == 'horizontal';
    $.each(values, function(v) {
      var n = graph.getByName(v.label);
      if(n) {
        n.setData('valueArray', $.splat(v.values));
        if(json.label) {
          n.setData('stringArray', $.splat(json.label));
        }
      }
    });
    this.normalizeDims();
    delegate.compute();
    delegate.select(delegate.root);
    if(animate) {
      if(horz) {
        delegate.fx.animate({
          modes: ['node-property:width:dimArray'],
          duration:1500,
          onComplete: function() {
            that.busy = false;
            onComplete && onComplete.onComplete();
          }
        });
      } else {
        delegate.fx.animate({
          modes: ['node-property:height:dimArray'],
          duration:1500,
          onComplete: function() {
            that.busy = false;
            onComplete && onComplete.onComplete();
          }
        });
      }
    }
  },
  
  //adds the little brown bar when hovering the node
  select: function(id, name) {
    if(!this.config.hoveredColor) return;
    var s = this.selected;
    if(s.id != id || s.name != name) {
      s.id = id;
      s.name = name;
      s.color = this.config.hoveredColor;
      this.delegate.graph.eachNode(function(n) {
        if(id == n.id) {
          n.setData('border', s);
        } else {
          n.setData('border', false);
        }
      });
      this.delegate.plot();
    }
  },
  
  /*
    Method: getLegend
   
    Returns an object containing as keys the legend names and as values hex strings with color values.
    
    Example:
    
    (start code js)
    var legend = barChart.getLegend();
    (end code)
  */  
  getLegend: function() {
    var legend = {};
    var n;
    this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
      n = adj.nodeTo;
    });
    var colors = n.getData('colorArray'),
        len = colors.length;
    $.each(n.getData('stringArray'), function(s, i) {
      legend[s] = colors[i % len];
    });
    return legend;
  },
  
  /*
    Method: getMaxValue
   
    Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
    
    Example:
    
    (start code js)
    var ans = barChart.getMaxValue();

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

   labels - Access a <Sunburst.Label> interface implementation.   

*/

$jit.Sunburst = new Class({

  Implements: [ Loader, Extras, Layouts.Radial ],

  initialize: function(controller) {
    var $Sunburst = $jit.Sunburst;

    var config = {
      interpolation: 'linear',
      levelDistance: 100,
      Node: {
        'type': 'multipie',
        'height':0
      },
      Edge: {
        'type': 'none'
      },
      Label: {
        textAlign: 'start',
        textBaseline: 'middle'
      }
    };

    this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
        "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);

    var canvasConfig = this.config;
    if(canvasConfig.useCanvas) {
      this.canvas = canvasConfig.useCanvas;
      this.config.labelContainer = this.canvas.id + '-label';
    } else {
      if(canvasConfig.background) {
        canvasConfig.background = $.merge({
          type: 'Circles'
        }, canvasConfig.background);
      }
      this.canvas = new Canvas(this, canvasConfig);
      this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
    }

    this.graphOptions = {
      'klass': Polar,
      'Node': {
        'selected': false,
        'exist': true,
        'drawn': true
      }
    };
    this.graph = new Graph(this.graphOptions, this.config.Node,
        this.config.Edge);
    this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
    this.fx = new $Sunburst.Plot(this, $Sunburst);
    this.op = new $Sunburst.Op(this);
    this.json = null;
    this.root = null;
    this.rotated = null;
    this.busy = false;
    // initialize extras
    this.initializeExtras();
  },

  /* 
  
    createLevelDistanceFunc 
  
    Returns the levelDistance function used for calculating a node distance 
    to its origin. This function returns a function that is computed 
    per level and not per node, such that all nodes with the same depth will have the 
    same distance to the origin. The resulting function gets the 
    parent node as parameter and returns a float.

   */
  createLevelDistanceFunc: function() {
    var ld = this.config.levelDistance;
    return function(elem) {
      return (elem._depth + 1) * ld;
    };
  },

  /* 
     Method: refresh 
     
     Computes positions and plots the tree.

   */
  refresh: function() {
    this.compute();
    this.plot();
  },

  /*
   reposition
  
   An alias for computing new positions to _endPos_

   See also:

   <Sunburst.compute>
   
  */
  reposition: function() {
    this.compute('end');
  },

  /*
  Method: rotate
  
  Rotates the graph so that the selected node is horizontal on the right.

  Parameters:
  
  node - (object) A <Graph.Node>.
  method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
  opt - (object) Configuration options merged with this visualization configuration options.
  
  See also:

  <Sunburst.rotateAngle>
  
  */
  rotate: function(node, method, opt) {
    var theta = node.getPos(opt.property || 'current').getp(true).theta;
    this.rotated = node;
    this.rotateAngle(-theta, method, opt);
  },

  /*
  Method: rotateAngle
  
  Rotates the graph of an angle theta.
  
   Parameters:
   
   node - (object) A <Graph.Node>.
   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
   opt - (object) Configuration options merged with this visualization configuration options.
   
   See also:

   <Sunburst.rotate>
  
  */
  rotateAngle: function(theta, method, opt) {
    var that = this;
    var options = $.merge(this.config, opt || {}, {
      modes: [ 'polar' ]
    });
    var prop = opt.property || (method === "animate" ? 'end' : 'current');
    if(method === 'animate') {
      this.fx.animation.pause();
    }
    this.graph.eachNode(function(n) {
      var p = n.getPos(prop);
      p.theta += theta;
      if (p.theta < 0) {
        p.theta += Math.PI * 2;
      }
    });
    if (method == 'animate') {
      this.fx.animate(options);
    } else if (method == 'replot') {
      this.fx.plot();
      this.busy = false;
    }
  },

  /*
   Method: plot
  
   Plots the Sunburst. This is a shortcut to *fx.plot*.
  */
  plot: function() {
    this.fx.plot();
  }
});

$jit.Sunburst.$extend = true;

(function(Sunburst) {

  /*
     Class: Sunburst.Op

     Custom extension of <Graph.Op>.

     Extends:

     All <Graph.Op> methods
     
     See also:
     
     <Graph.Op>

  */
  Sunburst.Op = new Class( {

    Implements: Graph.Op

  });

  /*
     Class: Sunburst.Plot

    Custom extension of <Graph.Plot>.
  
    Extends:
  
    All <Graph.Plot> methods
    
    See also:
    
    <Graph.Plot>
  
  */
  Sunburst.Plot = new Class( {

    Implements: Graph.Plot

  });

  /*
    Class: Sunburst.Label

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

          ctx.fillStyle = ctx.strokeStyle = label.color;
          var scale = resizeLabels? node.getData('normalizedDim') : 1,
              fontSize = (label.size * scale) >> 0;
          fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
          
          ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
          ctx.textBaseline = 'middle';
          ctx.textAlign = 'center';
          
          polar.rho = acum + config.labelOffset + config.sliceOffset;
          polar.theta = node.pos.theta;
          var cart = polar.getc(true);
          
          ctx.fillText(node.name, cart.x, cart.y);
          ctx.restore();
        }
      }
    },
    'contains': function(node, pos) {
      if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
        var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
        var ld = this.config.levelDistance, d = node._depth;
        var config = node.getData('config');
        if(rho <=ld * d + config.sliceOffset) {
          var dimArray = node.getData('dimArray');
          for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
            var dimi = dimArray[i];
            if(rho >= acum && rho <= acum + dimi) {
              return {
                name: node.getData('stringArray')[i],
                color: node.getData('colorArray')[i],
                value: node.getData('valueArray')[i],
                label: node.name
              };
            }
            acum += dimi;
          }
        }
        return false;
        
      }
      return false;
    }
  }
});

/*
  Class: PieChart
  
  A visualization that displays stacked bar charts.
  
  Constructor Options:
  
  See <Options.PieChart>.

*/
$jit.PieChart = new Class({
  sb: null,
  colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
  selected: {},
  busy: false,
  
  initialize: function(opt) {
    this.controller = this.config = 
      $.merge(Options("Canvas", "PieChart", "Label"), {
        Label: { type: 'Native' }
      }, opt);
    this.initializeViz();
  },
  
  initializeViz: function() {
    var config = this.config, that = this;
    var nodeType = config.type.split(":")[0];
    var delegate = new $jit.Sunburst({
      injectInto: config.injectInto,
      width: config.width,
      height: config.height,
      useCanvas: config.useCanvas,
      withLabels: config.Label.type != 'Native',
      Label: {
        type: config.Label.type
      },
      Node: {
        overridable: true,
        type: 'piechart-' + nodeType,
        width: 1,
        height: 1
      },
      Edge: {
        type: 'none'
      },
      Tips: {
        enable: config.Tips.enable,
        type: 'Native',
        force: true,
        onShow: function(tip, node, contains) {
          var elem = contains;
          config.Tips.onShow(tip, elem, node);
        }
      },
      Events: {
        enable: true,
        type: 'Native',
        onClick: function(node, eventInfo, evt) {
          if(!config.Events.enable) return;
          var elem = eventInfo.getContains();
          config.Events.onClick(elem, eventInfo, evt);
        },
        onMouseMove: function(node, eventInfo, evt) {
          if(!config.hoveredColor) return;
          if(node) {
            var elem = eventInfo.getContains();
            that.select(node.id, elem.name, elem.index);
          } else {
            that.select(false, false, false);
          }
        }
      },
      onCreateLabel: function(domElement, node) {
        var labelConf = config.Label;
        if(config.showLabels) {

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

      var val = values[i];
      var valArray = $.splat(val.values);
      ch.push({
        'id': prefix + val.label,
        'name': val.label,
        'data': {
          'value': valArray,
          '$valueArray': valArray,
          '$colorArray': mono? $.splat(color[i % colorLength]) : color,
          '$stringArray': name,
          '$gradient': gradient,
          '$config': config,
          '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
        },
        'children': []
      });
    }
    var root = {
      'id': prefix + '$root',
      'name': '',
      'data': {
        '$type': 'none',
        '$width': 1,
        '$height': 1
      },
      'children': ch
    };
    delegate.loadJSON(root);
    
    this.normalizeDims();
    delegate.refresh();
    if(animate) {
      delegate.fx.animate({
        modes: ['node-property:dimArray'],
        duration:1500
      });
    }
  },
  
  /*
    Method: updateJSON
   
    Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
    
    Parameters:
    
    json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
    onComplete - (object) A callback object to be called when the animation transition when updating the data end.
    
    Example:
    
    (start code js)
    pieChart.updateJSON(json, {
      onComplete: function() {
        alert('update complete!');
      }
    });
    (end code)
  */  
  updateJSON: function(json, onComplete) {
    if(this.busy) return;
    this.busy = true;
    
    var delegate = this.delegate;
    var graph = delegate.graph;
    var values = json.values;
    var animate = this.config.animate;
    var that = this;
    $.each(values, function(v) {
      var n = graph.getByName(v.label),
          vals = $.splat(v.values);
      if(n) {
        n.setData('valueArray', vals);
        n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
        if(json.label) {
          n.setData('stringArray', $.splat(json.label));
        }
      }
    });
    this.normalizeDims();
    if(animate) {
      delegate.compute('end');
      delegate.fx.animate({
        modes: ['node-property:dimArray:span', 'linear'],
        duration:1500,
        onComplete: function() {
          that.busy = false;
          onComplete && onComplete.onComplete();
        }
      });
    } else {
      delegate.refresh();
    }
  },
    
  //adds the little brown bar when hovering the node
  select: function(id, name) {
    if(!this.config.hoveredColor) return;
    var s = this.selected;
    if(s.id != id || s.name != name) {
      s.id = id;
      s.name = name;
      s.color = this.config.hoveredColor;
      this.delegate.graph.eachNode(function(n) {
        if(id == n.id) {
          n.setData('border', s);
        } else {
          n.setData('border', false);
        }
      });
      this.delegate.plot();
    }
  },
  
  /*
    Method: getLegend
   
    Returns an object containing as keys the legend names and as values hex strings with color values.
    
    Example:
    
    (start code js)
    var legend = pieChart.getLegend();
    (end code)
  */  
  getLegend: function() {
    var legend = {};
    var n;
    this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
      n = adj.nodeTo;
    });
    var colors = n.getData('colorArray'),
        len = colors.length;
    $.each(n.getData('stringArray'), function(s, i) {
      legend[s] = colors[i % len];
    });
    return legend;
  },
  
  /*
    Method: getMaxValue
   
    Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
    
    Example:
    
    (start code js)

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

      this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
    }

    this.graphOptions = {
      'klass': Complex,
      'Node': {
        'selected': false,
        'exist': true,
        'drawn': true
      }
    };

    this.graph = new Graph(
      this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);

    this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
    this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
    this.op = new $jit.Icicle.Op(this);
    this.group = new $jit.Icicle.Group(this);
    this.clickedNode = null;

    this.initializeExtras();
  },

  /* 
    Method: refresh 
    
    Computes positions and plots the tree.
  */
  refresh: function(){
    var labelType = this.config.Label.type;
    if(labelType != 'Native') {
      var that = this;
      this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
    }
    this.compute();
    this.plot();
  },

  /* 
    Method: plot 
    
    Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
  
   */
  plot: function(){
    this.fx.plot(this.config);
  },

  /* 
    Method: enter 
    
    Sets the node as root.
    
     Parameters:
     
     node - (object) A <Graph.Node>.
  
   */
  enter: function (node) {
    if (this.busy)
      return;
    this.busy = true;

    var that = this,
        config = this.config;

    var callback = {
      onComplete: function() {
        //compute positions of newly inserted nodes
        if(config.request)
          that.compute();

        if(config.animate) {
          that.graph.nodeList.setDataset(['current', 'end'], {
            'alpha': [1, 0] //fade nodes
          });

          Graph.Util.eachSubgraph(node, function(n) {
            n.setData('alpha', 1, 'end');
          }, "ignore");

          that.fx.animate({
            duration: 500,
            modes:['node-property:alpha'],
            onComplete: function() {
              that.clickedNode = node;
              that.compute('end');

              that.fx.animate({
                modes:['linear', 'node-property:width:height'],
                duration: 1000,
                onComplete: function() {
                  that.busy = false;
                  that.clickedNode = node;
                }
              });
            }
          });
        } else {
          that.clickedNode = node;
          that.busy = false;
          that.refresh();
        }
      }
    };

    if(config.request) {
      this.requestNodes(clickedNode, callback);
    } else {
      callback.onComplete();
    }
  },

  /* 
    Method: out 
    
    Sets the parent node of the current selected node as root.
  
   */
  out: function(){
    if(this.busy)
      return;

    var that = this,
        GUtil = Graph.Util,
        config = this.config,
        graph = this.graph,
        parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
        parent = parents[0],
        clickedNode = parent,
        previousClickedNode = this.clickedNode;

    this.busy = true;
    this.events.hoveredNode = false;

    if(!parent) {
      this.busy = false;
      return;
    }

    //final plot callback
    callback = {
      onComplete: function() {
        that.clickedNode = parent;
        if(config.request) {
          that.requestNodes(parent, {
            onComplete: function() {
              that.compute();
              that.plot();
              that.busy = false;
            }
          });
        } else {
          that.compute();
          that.plot();
          that.busy = false;
        }
      }
    };

    //animate node positions
    if(config.animate) {
      this.clickedNode = clickedNode;
      this.compute('end');
      //animate the visible subtree only
      this.clickedNode = previousClickedNode;
      this.fx.animate({
        modes:['linear', 'node-property:width:height'],
        duration: 1000,
        onComplete: function() {
          //animate the parent subtree
          that.clickedNode = clickedNode;
          //change nodes alpha
          graph.nodeList.setDataset(['current', 'end'], {
            'alpha': [0, 1]
          });
          GUtil.eachSubgraph(previousClickedNode, function(node) {
            node.setData('alpha', 1);
          }, "ignore");
          that.fx.animate({
            duration: 500,
            modes:['node-property:alpha'],
            onComplete: function() {
              callback.onComplete();
            }
          });
        }
      });
    } else {
      callback.onComplete();
    }
  },
  requestNodes: function(node, onComplete){
    var handler = $.merge(this.controller, onComplete),
        levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;

    if (handler.request) {
      var leaves = [], d = node._depth;
      Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
        if (n.drawn && !Graph.Util.anySubnode(n)) {
          leaves.push(n);
          n._level = n._depth - d;
          if (this.config.constrained)
            n._level = levelsToShow - n._level;

        }
      });
      this.group.requestNodes(leaves, handler);
    } else {
      handler.onComplete();
    }
  }
});

/*
  Class: Icicle.Op

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

   - <Options.Navigation>
   
   Additionally, there are two parameters
   
   levelDistance - (number) Default's *50*. The natural length desired for the edges.
   iterations - (number) Default's *50*. The number of iterations for the spring layout simulation. Depending on the browser's speed you could set this to a more 'interesting' number, like *200*. 
     
   Instance Properties:

   canvas - Access a <Canvas> instance.
   graph - Access a <Graph> instance.
   op - Access a <ForceDirected.Op> instance.
   fx - Access a <ForceDirected.Plot> instance.
   labels - Access a <ForceDirected.Label> interface implementation.

*/

$jit.ForceDirected = new Class( {

  Implements: [ Loader, Extras, Layouts.ForceDirected ],

  initialize: function(controller) {
    var $ForceDirected = $jit.ForceDirected;

    var config = {
      iterations: 50,
      levelDistance: 50
    };

    this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
        "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);

    var canvasConfig = this.config;
    if(canvasConfig.useCanvas) {
      this.canvas = canvasConfig.useCanvas;
      this.config.labelContainer = this.canvas.id + '-label';
    } else {
      if(canvasConfig.background) {
        canvasConfig.background = $.merge({
          type: 'Circles'
        }, canvasConfig.background);
      }
      this.canvas = new Canvas(this, canvasConfig);
      this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
    }

    this.graphOptions = {
      'klass': Complex,
      'Node': {
        'selected': false,
        'exist': true,
        'drawn': true
      }
    };
    this.graph = new Graph(this.graphOptions, this.config.Node,
        this.config.Edge);
    this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
    this.fx = new $ForceDirected.Plot(this, $ForceDirected);
    this.op = new $ForceDirected.Op(this);
    this.json = null;
    this.busy = false;
    // initialize extras
    this.initializeExtras();
  },

  /* 
    Method: refresh 
    
    Computes positions and plots the tree.
  */
  refresh: function() {
    this.compute();
    this.plot();
  },

  reposition: function() {
    this.compute('end');
  },

/*
  Method: computeIncremental
  
  Performs the Force Directed algorithm incrementally.
  
  Description:
  
  ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
  This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
  avoiding browser messages such as "This script is taking too long to complete".
  
  Parameters:
  
  opt - (object) The object properties are described below
  
  iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
  of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
  
  property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
  You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
  computations for final animation positions then you can just choose 'end'.
  
  onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
  parameter a percentage value.
  
  onComplete - A callback function called when the algorithm completed.
  
  Example:
  
  In this example I calculate the end positions and then animate the graph to those positions
  
  (start code js)
  var fd = new $jit.ForceDirected(...);
  fd.computeIncremental({
    iter: 20,
    property: 'end',
    onStep: function(perc) {
      Log.write("loading " + perc + "%");
    },
    onComplete: function() {
      Log.write("done");
      fd.animate();

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

      orientation: "h",
      titleHeight: 13,
      offset: 2,
      levelsToShow: 0,
      constrained: false,
      animate: false,
      Node: {
        type: 'rectangle',
        overridable: true,
        //we all know why this is not zero,
        //right, Firefox?
        width: 3,
        height: 3,
        color: '#444'
      },
      Label: {
        textAlign: 'center',
        textBaseline: 'top'
      },
      Edge: {
        type: 'none'
      },
      duration: 700,
      fps: 45
    };

    this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
        "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
    this.layout.orientation = this.config.orientation;

    var canvasConfig = this.config;
    if (canvasConfig.useCanvas) {
      this.canvas = canvasConfig.useCanvas;
      this.config.labelContainer = this.canvas.id + '-label';
    } else {
      if(canvasConfig.background) {
        canvasConfig.background = $.merge({
          type: 'Circles'
        }, canvasConfig.background);
      }
      this.canvas = new Canvas(this, canvasConfig);
      this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
    }

    this.graphOptions = {
      'klass': Complex,
      'Node': {
        'selected': false,
        'exist': true,
        'drawn': true
      }
    };
    this.graph = new Graph(this.graphOptions, this.config.Node,
        this.config.Edge);
    this.labels = new TM.Label[canvasConfig.Label.type](this);
    this.fx = new TM.Plot(this);
    this.op = new TM.Op(this);
    this.group = new TM.Group(this);
    this.geom = new TM.Geom(this);
    this.clickedNode = null;
    this.busy = false;
    // initialize extras
    this.initializeExtras();
  },

  /* 
    Method: refresh 
    
    Computes positions and plots the tree.
  */
  refresh: function(){
    if(this.busy) return;
    this.busy = true;
    var that = this;
    if(this.config.animate) {
      this.compute('end');
      this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
          && this.clickedNode.id || this.root));
      this.fx.animate($.merge(this.config, {
        modes: ['linear', 'node-property:width:height'],
        onComplete: function() {
          that.busy = false;
        }
      }));
    } else {
      var labelType = this.config.Label.type;
      if(labelType != 'Native') {
        var that = this;
        this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
      }
      this.busy = false;
      this.compute();
      this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
          && this.clickedNode.id || this.root));
      this.plot();
    }
  },

  /* 
    Method: plot 
    
    Plots the TreeMap. This is a shortcut to *fx.plot*. 
  
   */
  plot: function(){
    this.fx.plot();
  },

  /* 
  Method: leaf 
  
  Returns whether the node is a leaf.
  
   Parameters:
   
   n - (object) A <Graph.Node>.

 */
  leaf: function(n){
    return n.getSubnodes([
        1, 1
    ], "ignore").length == 0;
  },
  
  /* 
  Method: enter 
  
  Sets the node as root.
  
   Parameters:
   
   n - (object) A <Graph.Node>.

 */
  enter: function(n){
    if(this.busy) return;
    this.busy = true;
    
    var that = this,
        config = this.config,
        graph = this.graph,
        clickedNode = n,
        previousClickedNode = this.clickedNode;

    var callback = {
      onComplete: function() {
        //ensure that nodes are shown for that level
        if(config.levelsToShow > 0) {
          that.geom.setRightLevelToShow(n);
        }
        //compute positions of newly inserted nodes
        if(config.levelsToShow > 0 || config.request) that.compute();
        if(config.animate) {
          //fade nodes
          graph.nodeList.setData('alpha', 0, 'end');
          n.eachSubgraph(function(n) {
            n.setData('alpha', 1, 'end');
          }, "ignore");
          that.fx.animate({
            duration: 500,
            modes:['node-property:alpha'],
            onComplete: function() {
              //compute end positions
              that.clickedNode = clickedNode;
              that.compute('end');
              //animate positions
              //TODO(nico) commenting this line didn't seem to throw errors...
              that.clickedNode = previousClickedNode;
              that.fx.animate({
                modes:['linear', 'node-property:width:height'],
                duration: 1000,
                onComplete: function() { 
                  that.busy = false;
                  //TODO(nico) check comment above
                  that.clickedNode = clickedNode;
                }
              });
            }
          });
        } else {
          that.busy = false;
          that.clickedNode = n;
          that.refresh();
        }
      }
    };
    if(config.request) {
      this.requestNodes(clickedNode, callback);
    } else {
      callback.onComplete();
    }
  },

  /* 
  Method: out 
  
  Sets the parent node of the current selected node as root.

 */
  out: function(){
    if(this.busy) return;
    this.busy = true;
    this.events.hoveredNode = false;
    var that = this,
        config = this.config,
        graph = this.graph,
        parents = graph.getNode(this.clickedNode 
            && this.clickedNode.id || this.root).getParents(),
        parent = parents[0],
        clickedNode = parent,
        previousClickedNode = this.clickedNode;
    
    //if no parents return
    if(!parent) {
      this.busy = false;
      return;
    }
    //final plot callback
    callback = {
      onComplete: function() {
        that.clickedNode = parent;
        if(config.request) {
          that.requestNodes(parent, {
            onComplete: function() {
              that.compute();
              that.plot();
              that.busy = false;
            }
          });
        } else {
          that.compute();
          that.plot();
          that.busy = false;
        }
      }
    };
    //prune tree
    if (config.levelsToShow > 0)
      this.geom.setRightLevelToShow(parent);
    //animate node positions
    if(config.animate) {
      this.clickedNode = clickedNode;
      this.compute('end');
      //animate the visible subtree only
      this.clickedNode = previousClickedNode;
      this.fx.animate({
        modes:['linear', 'node-property:width:height'],
        duration: 1000,
        onComplete: function() {
          //animate the parent subtree
          that.clickedNode = clickedNode;
          //change nodes alpha
          graph.eachNode(function(n) {
            n.setDataset(['current', 'end'], {
              'alpha': [0, 1]
            });
          }, "ignore");
          previousClickedNode.eachSubgraph(function(node) {
            node.setData('alpha', 1);
          }, "ignore");
          that.fx.animate({
            duration: 500,
            modes:['node-property:alpha'],
            onComplete: function() {
              callback.onComplete();
            }
          });
        }
      });
    } else {
      callback.onComplete();
    }
  },

  requestNodes: function(node, onComplete){
    var handler = $.merge(this.controller, onComplete), 
        lev = this.config.levelsToShow;
    if (handler.request) {
      var leaves = [], d = node._depth;
      node.eachLevel(0, lev, function(n){
        var nodeLevel = lev - (n._depth - d);
        if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
          leaves.push(n);
          n._level = nodeLevel;
        }
      });
      this.group.requestNodes(leaves, handler);
    } else {
      handler.onComplete();
    }
  },
  
  reposition: function() {

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

   Additionally, there are other parameters and some default values changed
   
   interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
   levelDistance - (number) Default's *100*. The distance between levels of the tree. 
     
   Instance Properties:

   canvas - Access a <Canvas> instance.
   graph - Access a <Graph> instance.
   op - Access a <RGraph.Op> instance.
   fx - Access a <RGraph.Plot> instance.
   labels - Access a <RGraph.Label> interface implementation.   
*/

$jit.RGraph = new Class( {

  Implements: [
      Loader, Extras, Layouts.Radial
  ],

  initialize: function(controller){
    var $RGraph = $jit.RGraph;

    var config = {
      interpolation: 'linear',
      levelDistance: 100
    };

    this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
        "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);

    var canvasConfig = this.config;
    if(canvasConfig.useCanvas) {
      this.canvas = canvasConfig.useCanvas;
      this.config.labelContainer = this.canvas.id + '-label';
    } else {
      if(canvasConfig.background) {
        canvasConfig.background = $.merge({
          type: 'Circles'
        }, canvasConfig.background);
      }
      this.canvas = new Canvas(this, canvasConfig);
      this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
    }

    this.graphOptions = {
      'klass': Polar,
      'Node': {
        'selected': false,
        'exist': true,
        'drawn': true
      }
    };
    this.graph = new Graph(this.graphOptions, this.config.Node,
        this.config.Edge);
    this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
    this.fx = new $RGraph.Plot(this, $RGraph);
    this.op = new $RGraph.Op(this);
    this.json = null;
    this.root = null;
    this.busy = false;
    this.parent = false;
    // initialize extras
    this.initializeExtras();
  },

  /* 
  
    createLevelDistanceFunc 
  
    Returns the levelDistance function used for calculating a node distance 
    to its origin. This function returns a function that is computed 
    per level and not per node, such that all nodes with the same depth will have the 
    same distance to the origin. The resulting function gets the 
    parent node as parameter and returns a float.

   */
  createLevelDistanceFunc: function(){
    var ld = this.config.levelDistance;
    return function(elem){
      return (elem._depth + 1) * ld;
    };
  },

  /* 
     Method: refresh 
     
     Computes positions and plots the tree.

   */
  refresh: function(){
    this.compute();
    this.plot();
  },

  reposition: function(){
    this.compute('end');
  },

  /*
   Method: plot
  
   Plots the RGraph. This is a shortcut to *fx.plot*.
  */
  plot: function(){
    this.fx.plot();
  },
  /*
   getNodeAndParentAngle
  
   Returns the _parent_ of the given node, also calculating its angle span.
  */
  getNodeAndParentAngle: function(id){
    var theta = false;
    var n = this.graph.getNode(id);
    var ps = n.getParents();
    var p = (ps.length > 0)? ps[0] : false;
    if (p) {
      var posParent = p.pos.getc(), posChild = n.pos.getc();
      var newPos = posParent.add(posChild.scale(-1));
      theta = Math.atan2(newPos.y, newPos.x);
      if (theta < 0)
        theta += 2 * Math.PI;
    }
    return {
      parent: p,
      theta: theta
    };
  },
  /*
   tagChildren
  
   Enumerates the children in order to maintain child ordering (second constraint of the paper).
  */
  tagChildren: function(par, id){
    if (par.angleSpan) {
      var adjs = [];
      par.eachAdjacency(function(elem){
        adjs.push(elem.nodeTo);
      }, "ignore");
      var len = adjs.length;
      for ( var i = 0; i < len && id != adjs[i].id; i++)
        ;
      for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
        adjs[j].dist = k++;
      }
    }
  },
  /* 
  Method: onClick 
  
  Animates the <RGraph> to center the node specified by *id*.

   Parameters:

   id - A <Graph.Node> id.
   opt - (optional|object) An object containing some extra properties described below
   hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.

   Example:

   (start code js)
     rgraph.onClick('someid');
     //or also...
     rgraph.onClick('someid', {
      hideLabels: false
     });
    (end code)
    
  */
  onClick: function(id, opt){
    if (this.root != id && !this.busy) {
      this.busy = true;
      this.root = id;
      var that = this;
      this.controller.onBeforeCompute(this.graph.getNode(id));
      var obj = this.getNodeAndParentAngle(id);

      // second constraint
      this.tagChildren(obj.parent, id);
      this.parent = obj.parent;
      this.compute('end');

      // first constraint
      var thetaDiff = obj.theta - obj.parent.endPos.theta;
      this.graph.eachNode(function(elem){
        elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
      });

      var mode = this.config.interpolation;
      opt = $.merge( {
        onComplete: $.empty
      }, opt || {});

      this.fx.animate($.merge( {
        hideLabels: true,
        modes: [
          mode
        ]
      }, opt, {
        onComplete: function(){
          that.busy = false;
          opt.onComplete();
        }
      }));
    }
  }
});

$jit.RGraph.$extend = true;

(function(RGraph){

  /*
     Class: RGraph.Op
     
     Custom extension of <Graph.Op>.

     Extends:

     All <Graph.Op> methods
     
     See also:
     
     <Graph.Op>

  */
  RGraph.Op = new Class( {

    Implements: Graph.Op

  });

  /*
     Class: RGraph.Plot
    
    Custom extension of <Graph.Plot>.
  
    Extends:
  
    All <Graph.Plot> methods
    
    See also:
    
    <Graph.Plot>
  
  */
  RGraph.Plot = new Class( {

    Implements: Graph.Plot

  });

  /*
    Object: RGraph.Label

    Custom extension of <Graph.Label>. 
    Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
  
    Extends:
  
    All <Graph.Label> methods and subclasses.

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

  Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
  
  Instance Properties:
  
  canvas - Access a <Canvas> instance.
  graph - Access a <Graph> instance.
  op - Access a <Hypertree.Op> instance.
  fx - Access a <Hypertree.Plot> instance.
  labels - Access a <Hypertree.Label> interface implementation.

*/

$jit.Hypertree = new Class( {

  Implements: [ Loader, Extras, Layouts.Radial ],

  initialize: function(controller) {
    var $Hypertree = $jit.Hypertree;

    var config = {
      radius: "auto",
      offset: 0,
      Edge: {
        type: 'hyperline'
      },
      duration: 1500,
      fps: 35
    };
    this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
        "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);

    var canvasConfig = this.config;
    if(canvasConfig.useCanvas) {
      this.canvas = canvasConfig.useCanvas;
      this.config.labelContainer = this.canvas.id + '-label';
    } else {
      if(canvasConfig.background) {
        canvasConfig.background = $.merge({
          type: 'Circles'
        }, canvasConfig.background);
      }
      this.canvas = new Canvas(this, canvasConfig);
      this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
    }

    this.graphOptions = {
      'klass': Polar,
      'Node': {
        'selected': false,
        'exist': true,
        'drawn': true
      }
    };
    this.graph = new Graph(this.graphOptions, this.config.Node,
        this.config.Edge);
    this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
    this.fx = new $Hypertree.Plot(this, $Hypertree);
    this.op = new $Hypertree.Op(this);
    this.json = null;
    this.root = null;
    this.busy = false;
    // initialize extras
    this.initializeExtras();
  },

  /* 
  
  createLevelDistanceFunc 

  Returns the levelDistance function used for calculating a node distance 
  to its origin. This function returns a function that is computed 
  per level and not per node, such that all nodes with the same depth will have the 
  same distance to the origin. The resulting function gets the 
  parent node as parameter and returns a float.

  */
  createLevelDistanceFunc: function() {
    // get max viz. length.
    var r = this.getRadius();
    // get max depth.
    var depth = 0, max = Math.max, config = this.config;
    this.graph.eachNode(function(node) {
      depth = max(node._depth, depth);
    }, "ignore");
    depth++;
    // node distance generator
    var genDistFunc = function(a) {
      return function(node) {
        node.scale = r;
        var d = node._depth + 1;
        var acum = 0, pow = Math.pow;
        while (d) {
          acum += pow(a, d--);
        }
        return acum - config.offset;
      };
    };
    // estimate better edge length.
    for ( var i = 0.51; i <= 1; i += 0.01) {
      var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
      if (valSeries >= 2) { return genDistFunc(i - 0.01); }
    }
    return genDistFunc(0.75);
  },

  /* 
    Method: getRadius 
    
    Returns the current radius of the visualization. If *config.radius* is *auto* then it 
    calculates the radius by taking the smaller size of the <Canvas> widget.
    
    See also:
    
    <Canvas.getSize>
   
  */
  getRadius: function() {
    var rad = this.config.radius;
    if (rad !== "auto") { return rad; }
    var s = this.canvas.getSize();
    return Math.min(s.width, s.height) / 2;

lib/Devel/SizeMe/Graph/static/jit.js  view on Meta::CPAN

  },

  /* 
   Method: plot 
   
   Plots the <Hypertree>. This is a shortcut to *fx.plot*. 

  */
  plot: function() {
    this.fx.plot();
  },

  /* 
   Method: onClick 
   
   Animates the <Hypertree> to center the node specified by *id*.

   Parameters:

   id - A <Graph.Node> id.
   opt - (optional|object) An object containing some extra properties described below
   hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.

   Example:

   (start code js)
     ht.onClick('someid');
     //or also...
     ht.onClick('someid', {
      hideLabels: false
     });
    (end code)
    
  */
  onClick: function(id, opt) {
    var pos = this.graph.getNode(id).pos.getc(true);
    this.move(pos, opt);
  },

  /* 
   Method: move 

   Translates the tree to the given position. 

   Parameters:

   pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
   opt - This object has been defined in <Hypertree.onClick>
   
   Example:
   
   (start code js)
     ht.move({ x: 0, y: 0.7 }, {
       hideLabels: false
     });
   (end code)

  */
  move: function(pos, opt) {
    var versor = $C(pos.x, pos.y);
    if (this.busy === false && versor.norm() < 1) {
      this.busy = true;
      var root = this.graph.getClosestNodeToPos(versor), that = this;
      this.graph.computeLevels(root.id, 0);
      this.controller.onBeforeCompute(root);
      opt = $.merge( {
        onComplete: $.empty
      }, opt || {});
      this.fx.animate($.merge( {
        modes: [ 'moebius' ],
        hideLabels: true
      }, opt, {
        onComplete: function() {
          that.busy = false;
          opt.onComplete();
        }
      }), versor);
    }
  }
});

$jit.Hypertree.$extend = true;

(function(Hypertree) {

  /* 
     Class: Hypertree.Op 
   
     Custom extension of <Graph.Op>.

     Extends:

     All <Graph.Op> methods
     
     See also:
     
     <Graph.Op>

  */
  Hypertree.Op = new Class( {

    Implements: Graph.Op

  });

  /* 
     Class: Hypertree.Plot 
   
    Custom extension of <Graph.Plot>.
  
    Extends:
  
    All <Graph.Plot> methods
    
    See also:
    
    <Graph.Plot>
  
  */
  Hypertree.Plot = new Class( {

    Implements: Graph.Plot

  });

  /*
    Object: Hypertree.Label

    Custom extension of <Graph.Label>. 
    Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
  
    Extends:
  
    All <Graph.Label> methods and subclasses.



( run in 1.343 second using v1.01-cache-2.11-cpan-75ffa21a3d4 )