Author: tim.bunce
Date: Sun Jun 7 14:32:32 2009
New Revision: 762
Added:
trunk/lib/Devel/NYTProf/js/js-treemap/
trunk/lib/Devel/NYTProf/js/js-treemap-readme.txt
trunk/lib/Devel/NYTProf/js/js-treemap/BoxAnimator.js
trunk/lib/Devel/NYTProf/js/js-treemap/Decorators.js
trunk/lib/Devel/NYTProf/js/js-treemap/DivTreeMap.js
trunk/lib/Devel/NYTProf/js/js-treemap/NodeFacade.js
trunk/lib/Devel/NYTProf/js/js-treemap/Rectangle.js
trunk/lib/Devel/NYTProf/js/js-treemap/TreeMap.js
trunk/lib/Devel/NYTProf/js/js-treemap/TreeNode.js
trunk/lib/Devel/NYTProf/js/js-treemap/TreeNodeAdaptor.js
trunk/lib/Devel/NYTProf/js/js-treemap/TreeNodeShader.js
trunk/lib/Devel/NYTProf/js/js-treemap/Util.js
trunk/lib/Devel/NYTProf/js/js-treemap/XmlAdaptor.js
trunk/lib/Devel/NYTProf/js/js-treemap/XmlShader.js
Removed:
trunk/lib/Devel/NYTProf/js/uk.org.zowie.Treemap-0.9.js
Modified:
trunk/bin/nytprofhtml
Log:
Switched to using raw (non-minimized) src code of js-treemap.
Modified: trunk/bin/nytprofhtml
==============================================================================
--- trunk/bin/nytprofhtml (original)
+++ trunk/bin/nytprofhtml Sun Jun 7 14:32:32 2009
@@ -666,8 +666,20 @@
open(OUT, '>', "$opt{out}/$filename")
or croak "Unable to open file $opt{out}/$filename: $!";
- my $treemap_head = q{
- <script type="text/javascript"
src="js/uk.org.zowie.Treemap-0.9.js" ></script>
+ my $jstmdir = "js/js-treemap";
+ my $treemap_head = qq{
+ <script language="JavaScript" src="$jstmdir/BoxAnimator.js"></script>
+ <script language="JavaScript" src="$jstmdir/Decorators.js"></script>
+ <script language="JavaScript" src="$jstmdir/TreeMap.js"></script>
+ <script language="JavaScript" src="$jstmdir/DivTreeMap.js"></script>
+ <script language="JavaScript" src="$jstmdir/NodeFacade.js"></script>
+ <script language="JavaScript" src="$jstmdir/Rectangle.js"></script>
+ <script language="JavaScript" src="$jstmdir/TreeNode.js"></script>
+ <script language="JavaScript"
src="$jstmdir/TreeNodeAdaptor.js"></script>
+ <script language="JavaScript" src="$jstmdir/TreeNodeShader.js"></script>
+ <script language="JavaScript" src="$jstmdir/Util.js"></script>
+ <script language="JavaScript" src="$jstmdir/XmlAdaptor.js"></script>
+ <script language="JavaScript" src="$jstmdir/XmlShader.js"></script>
};
print OUT get_html_header("Package Treemap - NYTProf", {
Added: trunk/lib/Devel/NYTProf/js/js-treemap-readme.txt
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap-readme.txt Sun Jun 7 14:32:32
2009
@@ -0,0 +1 @@
+svn export
https://js-treemap.svn.sourceforge.net/svnroot/js-treemap/js-treemap/src/
js-treemap
Added: trunk/lib/Devel/NYTProf/js/js-treemap/BoxAnimator.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/BoxAnimator.js Sun Jun 7
14:32:32 2009
@@ -0,0 +1,66 @@
+/**
+ * Create an instance of a BoxAnimator
+ * @constructor
+ * @param div The source DIV node to animator
+ * @param {Rectangle} newCoords The coordinates to animate the DIV unto
+ * @param {number} steps The number of animation steps to take
+ * @param {number} millis The number of milliseconds between each step
+ * @param {function} afters a callback function to execute once the
animation is complete
+ *
+ * TODO: enlarge text label as part of animation
+ */
+function BoxAnimator( div, newCoords, steps, millis ) {
+ // Get current coords
+ this.div = div;
+ this.oldCoords = Rectangle.fromDIV( div );
+ this.newCoords = newCoords;
+ this.steps = steps;
+ this.millis = millis;
+ this.stepTime = Math.floor( millis/steps );
+
+ // Establish default values for callbacks
+ this.afters = this.before = null;
+}
+
+/**
+ * Begin the animations
+ */
+BoxAnimator.prototype.animate = function()
+{
+ if( this.before !== null ) { this.before(); }
+
+ // Raise the div above the rest
+ this.div.style.zIndex = 10;
+ this.setPaint( 1 );
+};
+
+/**
+ * Paint an single step of the animation, or call the 'afters' callback
+ * @private
+ */
+BoxAnimator.prototype.paintStep = function( stepNumber )
+{
+ if( stepNumber > this.steps ) {
+ // Finally, call the next step in a bit
+ if( this.afters !== null ) this.afters();
+ } else {
+ // Interpolate the coordinates by step/steps
+ var stepCoords = this.oldCoords.interpolate( this.newCoords,
stepNumber/this.steps );
+ stepCoords.moveDIV( this.div );
+
+ stepNumber++;
+ this.setPaint( stepNumber );
+ }
+};
+
+/**
+ * Set the timer for the next animation step
+ * @private
+ */
+BoxAnimator.prototype.setPaint = function( stepNumber )
+{
+ var self = this;
+ window.setTimeout( function() {
+ self.paintStep( stepNumber );
+ }, self.stepTime );
+};
\ No newline at end of file
Added: trunk/lib/Devel/NYTProf/js/js-treemap/Decorators.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/Decorators.js Sun Jun 7 14:32:32
2009
@@ -0,0 +1,48 @@
+/**
+ * A simple DIV decorator - that applies a CSS class - and that's it
+ * @constructor
+ * @param {string} cssClassName A CSS style name to apply
+ * A decorator need only implement a method 'decorate'
+ */
+function CssDecorator( cssClassName )
+{
+ this.cssClassName = cssClassName;
+}
+
+/**
+ * decorate a given DIV
+ * @param div the HTML DIV to decorate
+ * @param node the raw data node behind this DIV
+ * @param {Rectangle} coords The relative coordinates of this DIV
+ */
+CssDecorator.prototype.decorate = function( div, nodeFacade )
+{
+ div.className = this.cssClassName;
+};
+//
============================================================================
+
+/**
+ * The default DIV decorator - applies something like a useful style to a
given DIV
+ * @constructor
+ * @param {string} cssClassName A CSS style name to apply
+ */
+function DefaultDecorator()
+{
+ //LOW: figure out how to create a CSS class at runtime & apply that
instead
+}
+
+DefaultDecorator.prototype = new CssDecorator();
+
+/**
+ * decorate a given DIV
+ * @param div the HTML DIV to decorate
+ * @param node the raw data node behind this DIV
+ * @param {Rectangle} coords The relative coordinates of this DIV
+ */
+DefaultDecorator.prototype.decorate = function( div, nodeFacade )
+{
+ var style = div.style;
+ style.borderWidth = "1px";
+ style.borderStyle = "outset";
+// style.cursor = "default";
+};
\ No newline at end of file
Added: trunk/lib/Devel/NYTProf/js/js-treemap/DivTreeMap.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/DivTreeMap.js Sun Jun 7 14:32:32
2009
@@ -0,0 +1,283 @@
+//TODO: fix the minor overflow problems.
+
+/**
+ * Create a TreeMap widget
+ * @constructor
+ * @param {div} rootDIV A DIV element in the HTML DOM for use by the
treemap
+ * @param dataRoot the data hierarchy the treemap is to explore. This must
be a TreeParentNode unless an adaptor is given in the options argument
+ * @param options Optional settings for the object
+ * @param options.shader A Shader implementation that is used to colour
individual boxes in the treemap
+ * @param options.decorator A Decorator implementation, used to decorate
individual boxes in the treemap
+ * @param options.adaptor An adaptor object, used by the treemap to
interogae the dataRoot object. Only necessary if dataRoot is not a
TreeMapParent instance
+ */
+function DivTreeMap( rootDIV, dataRoot, options )
+{
+ TreeMap.call( this, ( "adaptor" in options ) ? options.adaptor : new
TreeNodeAdaptor() ); // Call the parent constructor
+
+ // Process arguments
+ this.rootDIV = rootDIV;
+ this.displayNode = this.rootNode = dataRoot;
+ this.dimensions = { width: 0, height:0 };
+
+ // Some internal state
+ this.selected = null; // currently/last 'selected' DIV
+ this.animSteps = 10;
+ this.animDuration = 500;
+
+ // Process options
+ this.shader = ( "shader" in options ) ? options.shader : null;
+ this.decorator = ( "decorator" in options ) ? options.decorator : new
DefaultDecorator();
+
+ // Record of steps into the data
+ this.history = new Array();
+
+ // MUST make the root container relatively positioned - to position
text
usefully
+ this.rootDIV.style.position = "relative";
+
+ this.repaint();
+// this.paint( this.displayNode, new Rectangle( 0, 0,
this.rootDIV.clientWidth, this.rootDIV.clientHeight ) );
+}
+
+/**
+ * Ensure that DivTreeMap inherits from TreeMap
+ */
+DivTreeMap.prototype = new TreeMap();
+
+/**
+ * Space between a box label and it's parent DIV
+ */
+DivTreeMap.LEFT_MARGIN = 5; // LOW: arbitrary constant
+
+
+DivTreeMap.prototype.requestPaint = function( displayNode, displayRect,
level )
+{
+ var cursorStyle = this.rootDIV.style.cursor;
+ this.rootDIV.style.cursor = "wait";
+
+ // Clear the content
+ this.clear();
+
+ var self = this;
+ window.setTimeout( function() {
+ self.repaint();
+ }, 100 );
+
+ // redraw the lot
+ this.paint( this.displayNode, new Rectangle( 0, 0,
this.rootDIV.clientWidth, this.rootDIV.clientHeight ) );
+
+ // Restore cursor style
+ this.rootDIV.style.cursor = cursorStyle;
+};
+
+/**
+ * Clear and then paint the control
+ * Used when (un)zooming and resizing
+ * @private
+ */
+DivTreeMap.prototype.repaint = function( )
+{
+ // Clear the content
+ this.clear();
+
+ // redraw the lot
+ this.paint( this.displayNode, new Rectangle( 0, 0,
this.rootDIV.clientWidth, this.rootDIV.clientHeight ) );
+};
+
+/**
+ * Paint this treemap
+ * @param displayNode data node used to render display
+ * @param {Rectangle} displayRect Rectangle of space to consume during
rendition
+ * @param {number} level Depth into the overall hierarchy - displayNode
may not be the rootNode
+ * @private
+ */
+DivTreeMap.prototype.paint = function( displayNode, displayRect, level )
+{
+ if( arguments.length != 3 )
+ level = this.adaptor.getLevel( displayNode );
+
+ // Place all the items inside the given space
+ var nodeFacades = this.squarify( displayNode, displayRect );
+
+ for( var i=0, l=nodeFacades.length; i<l; i++ )
+ {
+ var facade = nodeFacades[i];
+ var coords = facade.getCoords();
+
+ // Parent the box div
+ var box = document.createElement( "div" );
+ box.style.position = "absolute";
+ coords.moveDIV( box );
+
+ // Longer term - there may be something to say for merging the
shader &
decorator iterfaces
+ if( this.decorator ) this.decorator.decorate( box, facade );
+ if( this.shader ) box.style.background =
this.shader.getBackground(
level );
+
+ box.node = facade.getNode();
+ this.rootDIV.appendChild( box );
+
+ // Stick a text label in there ... it's tempting to add it to
the box
DIV, but that causes issues ...
+ var isParentNode = ! facade.isLeaf();
+ var label = document.createElement( isParentNode ? "a" : "div"
);
+ label.style.position = "absolute";
+ label.style.left = coords.x + "px";
+ label.style.top = coords.y + "px";
+ label.style.marginLeft = DivTreeMap.LEFT_MARGIN + "px";
+ label.innerHTML = facade.getName();
+ if( this.shader ) label.style.color =
this.shader.getForeground( level );
+ this.rootDIV.appendChild( label );
+
+ // The magic heuristic: if the box label is too big - don't
render the
box at all
+ // Get the width/height of the label - is it larger than the
destined
cell?
+ if( label.clientWidth + DivTreeMap.LEFT_MARGIN > coords.width
||
label.clientHeight > coords.height )
+ {
+ // label.client* are not set until after they're
parented, so we have
to do this at least once
+ this.rootDIV.removeChild( box );
+ this.rootDIV.removeChild( label );
+ continue;
+ }
+
+ // Recurse into the child node - if sensible
+ if( isParentNode )
+ {
+ label.onclick = this.createCallback( "onZoomClick",
facade.getNode(),
box, true );
+ label.href="#"; // No HREF, and it doesn't render as a
link
+
+ // Shrink the coordinates to show the parent box
+ var subRect = facade.getCoords().shrink(
label.clientHeight );
+ if( subRect !== null )
+ this.paint( facade.getNode(), subRect, level +1
);
+ } else {
+ label.onclick = this.createCallback( "onBoxClick",
facade.getNode(),
box, true );
+ }
+
+ var dataNode = facade.getNode();
+
+ // Some minimal event handling
+ box.onclick = this.createCallback( "onBoxClick",
facade.getNode(), box,
true );
+
+ // Hook up other events
+ box.onmouseover = this.createCallback( "onMouseOver",
facade.getNode(),
box, false );
+ box.onmouseout = this.createCallback( "onMouseOut",
facade.getNode(),
box, false );
+ }
+
+ // Record the GUI size
+ this.dimensions = { width: displayRect.width , height:
displayRect.height
};
+};
+
+/**
+ * Create a named callback
+ * @param {string} methodName Name of method to call on this, if it exists
+ * @param node data node to pass to the callback
+ * @param elem HTML DIV element to pass to the callback
+ * @param isSelectEvent Only true is this event is a 'select' event
+ * @private
+ */
+DivTreeMap.prototype.createCallback = function( methodName, node, elem,
isSelectEvent )
+{
+ var self = this;
+ // NB: var-args style of argument passing would be nice,
+ // but 'arguments' is NOT an array and therefore lack a 'shift' method
+ return function() {
+ if( isSelectEvent ) self.setSelected( node, elem );
+ if( methodName in self ) self[methodName]( node );
+ };
+};
+
+/**
+ * Zoom into the currently selected node, with animation
+ */
+DivTreeMap.prototype.zoom = function()
+{
+ if( !( "selected" in this ) )
+ throw "Nothing selected to zoom into";
+
+ var map = this;
+ var selected = this.getSelected();
+ var anim = new BoxAnimator( selected.div, this.getCoords(),
this.animSteps, this.animDuration );
+
+ anim.before = function() { map.rootDIV.style.cursor = "wait"; };
+ anim.afters = function() { map.doZoom(); map.rootDIV.style.cursor
= "default"; };
+ anim.animate();
+};
+
+/**
+ * Called once the animation is complete to effect the zoom
+ * @private
+ */
+DivTreeMap.prototype.doZoom = function()
+{
+ this.history.push( this.displayNode );
+ this.displayNode = this.getSelected().node;
+ this.repaint();
+ this.setSelected();
+} ;
+
+/**
+ * Unzoom the control a single step, if possible
+ * @return the number of steps left in the zoom history
+ */
+DivTreeMap.prototype.unzoom = function()
+{
+ if( this.history.length === 0 ) return 0;
+ this.displayNode = this.history.pop();
+ this.repaint();
+ this.setSelected();
+
+ return this.history.length;
+};
+
+/**
+ * Repaint the control if it has been resized
+ */
+DivTreeMap.prototype.checkResize = function( width, height )
+{
+ if( this.dimensions.width == this.rootDIV.clientWidth &&
+ this.dimensions.height == this.rootDIV.clientHeight ) return;
+
+ // otherwise ...
+ this.repaint();
+};
+
+/**
+ * Clear the contents of the control
+ * @private
+ */
+DivTreeMap.prototype.clear = function()
+{
+ // remove all DIVs and texts .. backwards
+ var children = this.rootDIV.childNodes;
+ for( var i=children.length-1; i >= 0 ;i-- )
+ this.rootDIV.removeChild( children[i] );
+};
+
+/**
+ * Get get relative coordinates of the control
+ * So x = y = 0 always
+ * @return {Rectangle} coordinates of ths control
+ */
+DivTreeMap.prototype.getCoords = function()
+{
+ return new Rectangle( 0, 0, this.dimensions.width,
this.dimensions.height
);
+};
+
+/**
+ * Return the DIV and the dataNode that are selected currently
+ * @return an object with properties 'div' and 'node'
+ */
+DivTreeMap.prototype.getSelected = function()
+{
+ return this.selected;
+};
+
+/**
+ * Set the currrent selected element, or unset one altogether
+ * @param node Optional data node that is 'selected'
+ * @param {DIV} Optional div the DIV currently 'selected'
+ */
+DivTreeMap.prototype.setSelected = function( node, div )
+{
+ this.selected = ( arguments.length === 0) ?
+ null:
+ { node: node, div: div };
+};
+
Added: trunk/lib/Devel/NYTProf/js/js-treemap/NodeFacade.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/NodeFacade.js Sun Jun 7 14:32:32
2009
@@ -0,0 +1,114 @@
+/**
+ * NodeFacade.js - 'Facade' for for the raw data objects - which are
occasionally inviolate
+ * ... meaning that extra properties cannot be added to them
+ * @constructor
+ * @param {Adaptor} adaptor Object used to interact with the data object
+ * @param node raw data object
+ */
+function NodeFacade( adaptor, node )
+{
+ this.adaptor = adaptor;
+ this.node = node;
+ this.coords = null;
+}
+
+/**
+ * Static 'bulk constructor'
+ * @param {Adaptor} adaptor Object used to interact with the data objects
+ * @param node Parent node to children that are to be wrapped
+ * @return an array of NodeFacade objects
+ */
+NodeFacade.wrapChildren = function( adaptor, node )
+{
+ var children = adaptor.getChildren( node );
+ var result = new Array( children.length );
+ for( var i=0, l=result.length; i<l ;i++ )
+ result[i] = new NodeFacade( adaptor, children[i] );
+ return result;
+};
+
+/**
+ * @return {String} a human useful string, describing this node-facade
+ */
+NodeFacade.prototype.toString = function()
+{
+ return this.coords + " node: " + this.node;
+};
+
+/**
+ * Compare one nodefacade with another
+ * An ideal fit for Array.sort()
+ * @param a A nodefacade object
+ * @param b Another nodefacade object
+ * @return {number} negative number if A>B, positive number if A<B, zero
otherwise
+ */
+NodeFacade.compare = function( a, b )
+{
+ return b.getValue() - a.getValue();
+};
+
+/**
+ * Fetch the value of the underlying node with the adaptor
+ * Using a cached local value - if at all possible
+ * @return {number} underlying node value
+ */
+NodeFacade.prototype.getValue = function()
+{
+ if( "value" in this ) return this.value;
+ return this.value = this.adaptor.getValue( this.node );
+};
+
+/**
+ * Fetch the name of the underlying node with the adaptor
+ * @return {string} underlying node name
+ */
+NodeFacade.prototype.getName = function()
+{
+ return this.value = this.adaptor.getName( this.node );
+};
+
+/**
+ * Fetch the children of the underlying node with the adaptor
+ * @return {Array} underlying node children
+ */
+NodeFacade.prototype.getChildren = function()
+{
+ return this.adaptor.getChildren( this.node );
+};
+
+/**
+ * Predicate - can this NodeFacade a leaf in the graph?
+ * @return {number} underlying node isLeaf value
+ */
+NodeFacade.prototype.isLeaf = function()
+{
+ return this.adaptor.isLeaf( this.node );
+};
+
+
+/**
+ * Set the coordinates
+ * @param {Rectangle} coords The new coordinates of this object
+ */
+NodeFacade.prototype.setCoords = function( coords )
+{
+ this.coords = coords;
+};
+
+/**
+ * Fetch the coordinates of this object
+ * @return {Rectangle} the coordinates of this object
+ */
+NodeFacade.prototype.getCoords = function()
+{
+ return this.coords;
+};
+
+/**
+ * Fetch the underlying data node
+ * @return the underlying data node
+ */
+NodeFacade.prototype.getNode = function()
+{
+ return this.node;
+};
\ No newline at end of file
Added: trunk/lib/Devel/NYTProf/js/js-treemap/Rectangle.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/Rectangle.js Sun Jun 7 14:32:32
2009
@@ -0,0 +1,122 @@
+ /**
+ * Construct a rectangle, with either zero or four arguments
+ * @constructor
+ * @param {number} x X coordinate of this rectangle
+ * @param {number} y Y coordinate of this rectangle
+ * @param {number} width Width of this rectangle
+ * @param {number} height Height of this rectangle
+ */
+function Rectangle( x, y, width, height )
+{
+ this.width = this.height = this.x = this.y = 0;
+ if( arguments.length != 4 ) return;
+
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ if(( width < 0 ) || ( height < 0 ) )alert( this + ": has negative width
"
);
+}
+
+/**
+ * Get the relative coordinates of a div,
+ * @param div HTML div to query for coordinates
+ * @return {Rectangle} coordinates of the given div
+ */
+Rectangle.fromDIV = function( div )
+{
+ return new Rectangle( div.offsetLeft, div.offsetTop, div.offsetWidth,
div.offsetHeight );
+};
+
+/**
+ * Create a copy of this rectangle
+ * @return {Rectangle} copy of this object
+ */
+Rectangle.prototype.clone = function()
+{
+ return new Rectangle( this.x, this.y, this.width, this.height );
+};
+
+/**
+ * Set a DIV to have these coordinates
+ * @param div HTML div to move
+ */
+Rectangle.prototype.moveDIV = function( div )
+{
+ var style = div.style;
+ style.left = this.x +"px";
+ style.top = this.y +"px";
+ style.width = this.width +"px";
+ style.height = this.height +"px";
+};
+
+/**
+ * @return a String representation of these coordinates
+ * Possibly a bit unnecessary with toSource()
+ */
+Rectangle.prototype.toString = function()
+{
+ return "(x=" + this.x + ", y=" + this.y + ", width = " + this.width +
",
height=" + this.height + ")";
+};
+
+/**
+ * Is this rectangle wider than it's tall?
+ * @return {boolean} true if this object is wider than tall
+ */
+Rectangle.prototype.isWide = function()
+{
+ return this.width > this.height;
+};
+
+/**
+ * Calculate a fraction of the distance between two numbers
+ * @private
+ * @return the fraction of the distance between two numbers
+ */
+function interp( from, to, fraction )
+{
+ return ( to - from ) * fraction;
+}
+
+/**
+ * Create a new rectangle that is interpolated between this and another
+ * @param {Rectangle} other Another Rectangle to interpolate towards
+ * @param {number} fraction a real number between 0 and 1, where 0 return
this object and 1 returns 'other'
+ * @return {Rectangle} a new rectangle between the two
+ */
+Rectangle.prototype.interpolate = function( other, fraction )
+{
+ // For each coordinate - get the difference
+ var result = this.clone();
+ result.x += interp( result.x, other.x, fraction );
+ result.y += interp( result.y, other.y, fraction );
+ result.width += interp( result.width, other.width, fraction );
+ result.height += interp( result.height, other.height, fraction );
+ return result;
+};
+
+
+/**
+ * Margin applied in shrink
+ * @private
+ */
+Rectangle.margin = 2;
+
+/**
+ * Createa a smaller Rectangle, or none at all
+ * @param {number} space in pixels to leave at the top
+ * @return {Rectangle} a smaller box, or null if any of the dimensions
become negative
+ *
+ */
+Rectangle.prototype.shrink = function( topD )
+{
+ var result = this.clone();
+ result.x += Rectangle.margin;
+ result.y += Rectangle.margin + topD;
+ result.width -= ( Rectangle.margin *2 );
+ result.height -= ( ( Rectangle.margin *2 ) + topD );
+
+ if( result.width <= 0 || result.height <= 0 ) return null;
+ return result;
+
+};
\ No newline at end of file
Added: trunk/lib/Devel/NYTProf/js/js-treemap/TreeMap.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/TreeMap.js Sun Jun 7 14:32:32
2009
@@ -0,0 +1,139 @@
+/**
+ * Base functionality for treemap implementations
+ * @constructor
+ * @param adaptor Data adaptor
+ */
+function TreeMap( adaptor )
+{
+ if( arguments.length === 0 ) return;
+ this.adaptor = adaptor;
+}
+
+TreeMap.HORIZONTAL = 1;
+TreeMap.VERTICAL = 2;
+
+/**
+ * Place boxes in the given rectangle, making each as square as possible,
wasting no space
+ * @param parentNode, parent to children that are placed
+ * @param {Rectangle} rootRect rectangle to fix boxes into
+ */
+TreeMap.prototype.squarify = function( parentNode, rootRect )
+{
+ var facades = NodeFacade.wrapChildren( this.adaptor, parentNode );
+
+ // Sort the set of blocks
+ facades.sort( NodeFacade.compare );
+
+ // Allocate space to all the nodes
+ this.divideDisplayArea( facades, rootRect );
+
+ return facades;
+};
+
+/**
+ * Tesselate the areas with the given areas into the given space
+ * @param {Array} facades An array of NodeFacades for placing
+ * @param {Rectangle} destRectangle Destination rectangle
+ * @private
+ */
+TreeMap.prototype.divideDisplayArea = function( facades, destRectangle )
+{
+ // Check for boundary conditions
+ if( facades.length === 0 ) return;
+
+ if( facades.length == 1 )
+ {
+ facades[0].setCoords( destRectangle );
+ return;
+ }
+
+ // Find the 'centre of gravity' for this node-list
+ var halves = this.splitFairly( facades );
+
+ // We can now divide up the available area into two
+ // parts according to the lists' sizes.
+ var midPoint;
+ var orientation;
+
+ var leftSum = this.sumValues( halves.left ),
+ rightSum = this.sumValues( halves.right ),
+ totalSum = leftSum + rightSum;
+
+ // Degenerate case: All size-zero entries.
+ if( leftSum + rightSum <= 0 )
+ {
+ midPoint = 0;
+ orientation = TreeMap.HORIZONTAL;
+ } else {
+
+ if( destRectangle.isWide() )
+ {
+ orientation = TreeMap.HORIZONTAL;
+ midPoint = Math.round( ( leftSum * destRectangle.width
) / totalSum );
+ } else {
+ orientation = TreeMap.VERTICAL;
+ midPoint = Math.round( ( leftSum * destRectangle.height
) / totalSum );
+ }
+ }
+
+ // Once we've split, we recurse to divide up the two
+ // new areas further, and we keep recursing until
+ // we're only trying to fit one entry into any
+ // given area. This way, even size-zero entries will
+ // eventually be assigned a location somewhere in the
+ // display. The rectangles below are created in
+ // (x, y, width, height) format.
+
+ if( orientation == TreeMap.HORIZONTAL )
+ {
+ this.divideDisplayArea( halves.left, new Rectangle(
destRectangle.x,
destRectangle.y, midPoint, destRectangle.height ) );
+ this.divideDisplayArea( halves.right, new Rectangle(
destRectangle.x +
midPoint, destRectangle.y, destRectangle.width - midPoint,
destRectangle.height ) );
+ } else {
+ this.divideDisplayArea( halves.left, new Rectangle(
destRectangle.x,
destRectangle.y, destRectangle.width, midPoint ) );
+ this.divideDisplayArea( halves.right, new Rectangle(
destRectangle.x,
destRectangle.y + midPoint, destRectangle.width, destRectangle.height -
midPoint ) );
+ }
+};
+
+/*
+ * Break the list in two by size, roughly
+ * @param {Array} facades An array of NodeFacades
+ * @private
+ * @return An object with fields 'left' and 'right' containing Arrays of
NodeFacade objects
+ */
+TreeMap.prototype.splitFairly = function( facades )
+{
+ var midPoint = 0;
+
+ if( this.sumValues( facades ) === 0 )
+ {
+ midPoint = Math.round( facades.length /2 ); // JS uses
floating-point
maths
+ } else {
+ var halfValue = this.sumValues( facades ) /2;
+ var accValue = 0;
+ for( var l=facades.length; midPoint< l; midPoint++ )
+ {
+ //NB: zeroth item _always_ goes into left-hand list
+ if( midPoint > 0 && ( accValue +
facades[midPoint].getValue() >
halfValue ) )
+ break;
+ accValue += facades[midPoint].getValue();
+ }
+ }
+
+ return {
+ left: facades.slice( 0, midPoint ),
+ right: facades.slice( midPoint )
+ };
+};
+
+/*
+ * Convenience function - return the sum of the values for an array of
facades
+ * @param {Array} facades An array of NodeFacade objects
+ * @private
+ */
+TreeMap.prototype.sumValues = function( facades )
+{
+ var result =0;
+ for( var i=0, l=facades.length; i<l; i++ )
+ result += facades[i].getValue();
+ return result;
+};
Added: trunk/lib/Devel/NYTProf/js/js-treemap/TreeNode.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/TreeNode.js Sun Jun 7 14:32:32
2009
@@ -0,0 +1,115 @@
+
+
+/**
+ * Create a simple TreeNode oObject
+ * @constructor
+ * @param {string} name Name of this TreeNode
+ * @param {number} value Value of this TreeNode
+ */
+function TreeNode( name, value )
+{
+ if( TreeNode.arguments.length === 0 ) return; // NB: this is a base
class
constructor .. sometimes
+ this.name = name;
+ this.value = value;
+ this.parent = null;
+}
+
+
+/**
+ * Get the value of this node
+ * A simple value accessor .. something TreeParentNode can overload
+ * @return {number} the value of this node
+ */
+TreeNode.prototype.getValue = function()
+{
+ return this.value;
+};
+
+/**
+ * Fetch a string representation for this object
+ * @return {String} the fully qualified name of this node
+ */
+TreeNode.prototype.toString = function()
+{
+ return this.getFqName() + "=" + this.value;
+};
+
+/**
+ * Get the name of this node, preceded by the name of all parent nodes
+ * @return {string} fully qualified node name
+ */
+TreeNode.prototype.getFqName= function()
+{
+ if( this.parent === null )
+ return this.name;
+ else
+ return this.parent.getFqName() + "/" + this.name;
+};
+
+//
============================================================================
+
+// The TreeParentNode class
+
+/**
+ * Construct a TreeParentNode object
+ * this inherits from the TreeNode class
+ * @constructor
+ * @param {string} name Name of this TreeNode
+ * @param children Array of TreeNode objects to contain
+ */
+function TreeParentNode( name, children )
+{
+ TreeNode.call( this, name, -1 );
+
+ // Some additional state
+ this.children = new Array();
+
+ // Parent the children & weed out cuckoos (cause by trailing commas in
IE
- bah)
+ for( var i=0, l=children.length; i<l; i++ )
+ {
+ if( children[i] instanceof TreeNode )
+ {
+ children[i].parent = this;
+ this.children.push( children[i] );
+ }
+ }
+}
+
+// Inherits from TreeNode class
+TreeParentNode.prototype = new TreeNode();
+
+/**
+ * Get the value of this object, from a cache if possible
+ * Otherwise recursively calculate it
+ * @retun {number} the value of this object
+ */
+TreeParentNode.prototype.getValue = function()
+{
+ var result = 0;
+ if( this.value < 0 ) // Check for cached values
+ {
+ for( var i=0, l=this.children.length; i<l; i++ )
+ result += this.children[i].getValue();
+ this.value = result;
+ }
+ return this.value;
+};
+
+/**
+ * How deep does this rabbit warren go?
+ * @return {number} number of levels this data model has
+ */
+TreeParentNode.prototype.countDepth = function()
+{
+ if( this.children.length === 0 ) return 0;
+
+ var childDepth = 0;
+ for( var i=0,l=this.children.length; i<l; i++ )
+ {
+ if( this.children[i] instanceof TreeParentNode )
+ {
+ childDepth = Math.max( childDepth,
this.children[i].countDepth() );
+ }
+ }
+ return 1 + childDepth;
+};
\ No newline at end of file
Added: trunk/lib/Devel/NYTProf/js/js-treemap/TreeNodeAdaptor.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/TreeNodeAdaptor.js Sun Jun 7
14:32:32 2009
@@ -0,0 +1,58 @@
+/**
+ * TreeNodeAdaptor.js - an adaptor for a simple TreeNode/TreeParentNode
hierarchy
+ */
+
+/**
+ * Create a TreeNodeAdaptor
+ * If JavaScript had abstract classes or interfaces, I would use them in
here and in XmlAdaptor
+ */
+function TreeNodeAdaptor()
+{
+ /* a do-nothing constructor */
+}
+
+/**
+ * Fetch the value of the given node
+ * @return {number} node value
+ */
+TreeNodeAdaptor.prototype.getValue = function( node )
+{
+ return node.getValue(); // Important for polymorphism
+};
+
+/**
+ * Fetch the value of the given node
+ * @return {number} node value
+ */
+TreeNodeAdaptor.prototype.getChildren = function( node )
+{
+ return node.children;
+};
+
+/**
+ * Fetch the name of the given node
+ * @return {string} node name
+ */
+TreeNodeAdaptor.prototype.getName = function( node )
+{
+ return node.name;
+};
+
+/**
+ * Predicate: is the given node a leaf node?
+ * @return {boolean}
+ */
+TreeNodeAdaptor.prototype.isLeaf = function( node )
+{
+ return !( node instanceof TreeParentNode );
+};
+
+/**
+ * Fetch the level of the given node in the overall model
+ * @return {number} the depth of the given node the model
+ */
+TreeNodeAdaptor.prototype.getLevel = function( node )
+{
+ if( node.parent === null ) return 0;
+ return 1 + this.getLevel( node.parent );
+};
Added: trunk/lib/Devel/NYTProf/js/js-treemap/TreeNodeShader.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/TreeNodeShader.js Sun Jun 7
14:32:32 2009
@@ -0,0 +1,58 @@
+/*
+ * Shader object, for use with TreeNode hierarchies
+ */
+
+/**
+ * Construct a TreeNodeShader
+ * @constructor
+ * @param rootNode a root TreeParentNode
+ */
+function TreeNodeShader( rootNode )
+{
+ this.levels = rootNode.countDepth();
+}
+
+/**
+ * Set of colours to use
+ */
+TreeNodeShader.purples = [
+ "#E6E6FA",
+ "#D8BFD8",
+ "#DDA0DD",
+ "#EE82EE",
+ "#DA70D6",
+ "#FF00FF",
+ "#BA55D3",
+ "#9370DB",
+ "#8A2BE2",
+ "#9400D3" ];
+
+/**
+ * Fetch a background colour
+ * @param {number} level Depth into the data model
+ * @return {string} a CSS colour string
+ */
+TreeNodeShader.prototype.getBackground = function( level )
+{
+ return TreeNodeShader.purples[ this.scale( level ) ];
+};
+
+/**
+ * Fetch a foreground colour
+ * @param {number} level Depth into the data model
+ * @return {string} a CSS colour string
+ */
+TreeNodeShader.prototype.getForeground = function( level )
+{
+ // LOW: fairly arbitrary judgement of when black text isn't clear enough
+ return ( this.scale( level ) <= ( TreeNodeShader.purples.length /2 )
) ? "black" : "white";
+};
+
+/**
+ * Scale a number by the ratio of level:levels
+ * @private
+ */
+TreeNodeShader.prototype.scale = function( level )
+{
+ return Math.floor( ( level * TreeNodeShader.purples.length ) /
this.levels );
+};
\ No newline at end of file
Added: trunk/lib/Devel/NYTProf/js/js-treemap/Util.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/Util.js Sun Jun 7 14:32:32 2009
@@ -0,0 +1,54 @@
+/**
+ * Find a parent node of a specific type
+ * @param node HTML node to begin the search at
+ * @param {string} parentTag element type to search fo
+ */
+function findParentNode( node, parentTag )
+{
+ if( node.tagName.toLowerCase() == parentTag.toLowerCase() ) return node;
+ return findParentNode( node.parentNode, parentTag );
+}
+
+/**
+ * Get the absolute coordinates of a given div
+ * @return Object with fields 'left' and 'top', both numbers
+ */
+function getAbsCoords( div )
+{
+ if( div === null )
+ {
+ return { left: 0, top: 0 };
+ } else {
+ var parentCoords = getAbsCoords( div.offsetParent );
+ return {
+ left: div.offsetLeft + parentCoords.left,
+ top: div.offsetTop + parentCoords.top
+ };
+ }
+}
+
+/**
+ * Create an AJAX request object, taking platform differences into some
account
+ * @return {XMLHttpRequest}
+ */
+function createAjaxRequest()
+{
+ if (window.XMLHttpRequest)
+ return new XMLHttpRequest();
+ else {
+ try {
+ return new ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch (ex) {
+ return new new ActiveXObject( "Msxml2.XMLHTTP" );
+ }
+ }
+}
+
+/**
+ * Is this running in Internet Explorer?
+ * @return {boolean}
+ */
+function isInternetExplorer()
+{
+ return navigator.appName.toLowerCase().search( "internet
explorer" ) !=
-1;
+}
\ No newline at end of file
Added: trunk/lib/Devel/NYTProf/js/js-treemap/XmlAdaptor.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/XmlAdaptor.js Sun Jun 7 14:32:32
2009
@@ -0,0 +1,67 @@
+/*
+ * XmlAdaptor.js - simple data adaptor for XML describing a directory
hierarchy
+ */
+
+/**
+ * Create a XmlAdaptor
+ * If JavaScript had abstract classes or interfaces, I would use them in
here and in TreeNodeAdaptor
+ */
+function XmlAdaptor()
+{
+ /* a do-nothing constructor */
+}
+
+/**
+ * Fetch the value of the given node
+ * @return {number} node value
+ */
+XmlAdaptor.prototype.getValue = function( elem )
+{
+ return parseInt( elem.getAttribute( "bytes" ), 10 );
+};
+
+/**
+ * Fetch the value of the given node
+ * @return {number} node value
+ */
+XmlAdaptor.prototype.getChildren = function( elem )
+{
+ var result = new Array();
+ for( var i=0, l=elem.childNodes.length; i<l; i++ )
+ {
+ if( elem.childNodes[i].nodeType == 1 )
+ {
+ result.push( elem.childNodes[i] );
+ }
+ }
+ return result;
+};
+
+/**
+ * Fetch the name of the given node
+ * @return {string} node name
+ */
+XmlAdaptor.prototype.getName = function( elem )
+{
+ return elem.getAttribute( "name" );
+};
+
+/**
+ * Predicate: is the given node a leaf node?
+ * @return {boolean}
+ */
+XmlAdaptor.prototype.isLeaf = function( elem )
+{
+ return elem.tagName == "file";
+};
+
+/**
+ * Fetch the level of the given node in the overall model
+ * @return {number} the depth of the given node the model
+ */
+XmlAdaptor.prototype.getLevel = function( elem )
+{
+ if( elem.parentNode == elem.ownerDocument ) return 0;
+ return 1 + this.getLevel( elem.parentNode );
+};
+
Added: trunk/lib/Devel/NYTProf/js/js-treemap/XmlShader.js
==============================================================================
--- (empty file)
+++ trunk/lib/Devel/NYTProf/js/js-treemap/XmlShader.js Sun Jun 7 14:32:32
2009
@@ -0,0 +1,62 @@
+/*
+ * Shader object, for use with XML hierarchies
+ */
+
+/**
+ * Construct an XmlShader
+ * @constructor
+ * @param xmlDoc an XML Document
+ */
+function XmlShader( xmlDoc )
+{
+ this.levels = this.countDepth( xmlDoc.documentElement );
+}
+
+/**
+ * Fetch a background colour
+ * @param {number} level Depth into the data model
+ * @return {string} a CSS colour string
+ */
+XmlShader.prototype.getBackground = function( level )
+{
+ return TreeNodeShader.purples[ this.scale( level ) ];
+};
+
+/**
+ * Fetch a foreground colour
+ * @param {number} level Depth into the data model
+ * @return {string} a CSS colour string
+ */
+XmlShader.prototype.getForeground = function( level )
+{
+ return ( this.scale( level ) <= ( TreeNodeShader.purples.length /2 )
) ? "black" : "white";
+};
+
+/**
+ * Scale a number by the ratio of level:levels
+ * @private
+ */
+XmlShader.prototype.scale = function( level )
+{
+ return Math.floor( ( level * TreeNodeShader.purples.length ) /
this.levels );
+};
+
+/**
+ * Recursively find the maximum depth of the given XML element
+ * @private
+ * @return {number}
+ */
+XmlShader.prototype.countDepth = function( elem )
+{
+ if( elem.childNodes.length === 0 ) return 0;
+
+ var childDepth = 0;
+ for( var i=0, l=elem.childNodes.length; i<l; i++ )
+ {
+ if( elem.childNodes[i].nodeName == "dir" )
+ {
+ childDepth = Math.max( childDepth, this.countDepth(
elem.childNodes[i]
) );
+ }
+ }
+ return 1 + childDepth;
+};
\ No newline at end of file
--~--~---------~--~----~------------~-------~--~----~
You've received this message because you are subscribed to
the Devel::NYTProf Development User group.
Group hosted at: http://groups.google.com/group/develnytprof-dev
Project hosted at: http://perl-devel-nytprof.googlecode.com
CPAN distribution: http://search.cpan.org/dist/Devel-NYTProf
To post, email: [email protected]
To unsubscribe, email: [email protected]
-~----------~----~----~----~------~----~------~--~---