Wednesday, September 22, 2010

DataSets and DataTables

A DataSet { doc / class }, in flare, is a collection artifact designed to offer a different collection option for graphs; as a result it only contains two dataTables { doc / class }: edges and nodes.

Neither DataSets or DataTables extend or inherit from any class; thus, other than the default methods for objects, the only information you'll find within datasets are the two datatables.
----------------------------------------------------------------------------------
I recently ran into these two while working with the graphML converter within flare; the graphML converter is designed for conversion between GraphML and flare DataSets.  I found that there are instances where the schema associated with a DataTable doesn't quite include some of the keys; thus, it makes sense to ensure they ARE included.

The write method instantiates the graphML object (of type XML), adds the schema for any nodes or edges in the DataSet, and follows it up with data from the same DataSet; there is no allowance for having a key included in the DataSet that isn't in the schema.

I presume this to be because the developer(s) assumed the graphML would be properly formed.

This is likely optimal - a process should adhere to its contract, be able to expect what it gets to maintain certain attributes, be able to process that foo appropriately, and be expected to spit out as the contract asserts.

You can't open a padlock with a swordfish.

Monday, May 24, 2010

Children

For me, one of  the most awkward words in ActionScript is that of child.  Children show up a great deal with addressing and including NodeSprites.  In fact, all display objects make use of children - a child is simply something attached in a particular way to another object.  So one display object might add another display object in a parent-child manner; the complexity comes in when identifying what manner the child was added.

The most obvious manner is to user the .addChild method.  This is clean and requires the added child to be of type displayObject.  Double adding a child has no effect - apparently it detects existing children.

The second most obvious manner is to use the .addChildAt method; the difference here is that, instead of simply adding it as if the parent were at stack, it offers the ability to add it in place of another existing child, or add it to the end.  Adding it at the end of the child array appends the standard addChild method; adding it anywhere else places the new child at the desired position and pushes all other children one further down the list.  Double adding a child using this method has no effect; much like earlier, if the desired index is out of scope of the parent child array, it will fail.

Regardless of which method, the number of children attached to a given parent display object may be found by using .numChildren.

Do not be misled by using the .childDegree property - this focuses on edges instead of nodes.  Instead of identifying the number of children by simply counting what children are attached to the parent node, one might instead go the route of looking to the number of child edges leaving a given node.  This might be intuitive - logically, each child of a parent should have an edge binding the two together.

This is not always the case.  In some architectures, the number of child edges may match, exceed, or be less than the number of children attached to a given parent node.  This is because the presence of an edge does not necessarily require the two nodes on either side of the edge to have a parent-child relationship.  It can also be the case that sometimes an edge will hold each of the nodes in question on either side of the edge, but the nodes will have a child-parent relationship; in these cases, simply having the child edge count is of necessity misleading.

Another concern when identifying children is the assumption of case; while looping through the children of a given parent, do not assume you know the type or the source.  I have seen at least one project where the children of a given parent-node had two types of children: NodeSprites and DisplayObjects (to which I would link but find the class to be unavailable).  Each has slightly different properties.  Likewise, although the displayObjects (as images) were cleanly available as children, the nodeSprite children were sometimes simply attached as the images, but were only completely available once one looped through the entire collection of nodes, identifying the nodes that had as a parent the original parent-node.

In short - do not be quick to assume you know the entire hierarchy or lineage of a given display object; it may be the case that other children exist, perhaps in the global node list, perhaps simply in the list of attached children, and perhaps hiding in the list of edges.

Friday, May 21, 2010

Event Listeners

One of the most powerful tools I've run across are Event Listeners.  In fact, I can safely say that, without Event Listeners, much of your application will appear lifeless.  Event Listeners allow your own drawn objects to respond to one another, to the mouse, to the keyboard, to events that happen outside them all (like a simple browser resize) and ultimately offer a means of providing interactivity.

I like to think of a listener as a link between a passive and an active.  You want the ball to roll, so you kick it.

The means by which a listener is associated with an object is through addition.  Specifically, most objects have a method called "addEventListener" which, simple enough, adds a listener - one triggered by a particular event.

There are different classes of Events.  There are generic Events (the browser resize mentioned earlier falls into this category), mouse-specific events (clicking is obvious, but also includes mouse-over and mouse out), and lots of others as well.  [Here] is the flash documentation for Events.

Making use of an Event Listener is fairly easy - the simplest requires knowing the object to which you would attach the listener (the ball), the event which you anticipate happening (the kick), and what you want to happen as a result (rolling).

Code samples:
Example with anonymous function
private function addButtonWithListener():void
{
  var button:TextSprite = new TextSprite();
  button.x = 50;
  button.y = 50;
  button.width = 10;
  button.height = 10;
  button.text = "blue!";
  addChild(button);

  button.addEventListener( MouseEvent.CLICK, function( m:MouseEvent):void
  {
      button.text = (button.text=="blue!") ? "red!" : "blue!";
  });
}

Example with a separate method (requiring a global button)
public class ListenerExample
{
    private var button:TextSprite;
  
    instantiateButton( 50, 50, 10, 10, "blue!" );
    addButtonListener();
  
    private function instantiateButton( _x:uint, _y:uint, _w:uint, _h:uint, initialText:String):void
    {
      button = new TextSprite();
      button.x = _x;
      button.y = _y;
      button.w = _w;
      button.h = _h;
      button.text = initialText;
      addChild( button );
    }

    private function addEventListener():void
    {
      button.addEventListener( MouseEvent.CLICK, toggleButtonText );
    }

    private function toggleButtonText( m:MouseEvent=null ):void
    {
      button.text = (button.text=="blue!") ? "red!" : "blue!";
    }
}

That should get you started.

Another, later post, will address removing event listeners.

Friday, April 23, 2010

Memory Errors / "Java heap space error"

While using the Eclipse environment, it is particularly easy to run out of room; if you've got a decent machine, it is easy to assume that the available RAM on the machine will automatically transfer to available build ram. This is not the case; thus, you may occasionally get errors that state Java heap space error or an Error that offers to close Eclipse.

Documentation exists in various locations; thus this post is intended to capture the notes I found useful from various sites. Where applicable I intend to link to the location I found the information (in case some further piece of information is of value to you).

=================================================

Setting the java heap size is fairly simple - it can be done in a number places.
The first (and potentially most obvious) is via command line; simply type them out.
example: java -Xmx128m
I am not terribly familiar with the command line interface for Eclipse, so I won't be documenting particulars; below, however, are some guidelines about how to set the parameters and what to watch out for when asserting them.

The second, and more obvious to me, is through the use of the initialization file, entitled "eclipse.ini". It is located in the directory in which you installed eclipse ... the one that has the "dropins" folder. Opening it up reveals several settings - the one you will be interested in is under "-vmargs".

The third is through the Eclipse interface proper.
I snagged the steps from here.

1. From the Eclipse main menu, choose "Window" then "Preferences".

2. On the Preferences dialog
Expand "Java" in the left-hand column
Under Java, click "Installed JREs"
You will now see your installed JREs with the one currently used being ticked
Select the "default" JRE and click the "Edit" button to the right of it.

3. Part way down the "Edit JRE" dialog, you'll see a field labeled "Default VM Arguments".
Go to this and put in your -X memory arguments.
I used "-Xms128M -Xmx512M".

Regardless of your preferred method, the two attributes are "-Xms80m" and "-Xmx256".
Xms refers to the start heap size, Xmx to the max heap size.

You need enough initial heap size to start building and you need enough maximum heap size to allow Eclipse/java to finish whatever tasks you've intended it to complete.

Several potential errors (link) in setting them include
  • missing units or inappropriate units
  • extra spaces, incorrect syntax, or non-whole units when assigning the amount of memory
  • assigning unreasonable memory sizes
Missing / inappropriate units
examples of missing a unit: "-Xms42" or "-Xmx100"
valid options for units are "m" or "g"; "mb" and "gb" are wrong
units are case insensitive; thus "m", "M", "g", and "G" are all valid units
Extra spaces, incorrect syntax, or non-whole units when assigning the amount of memory
parsing of the command requires no spacing or operator (like an equals sign)
correct ...  "-Xms75m"
incorrect ..."-Xms 75m"; "-Xms=75m"; "-Xms:75m"
in addition, separation of separate commands (like setting Xms and Xmx) is simple
if using the .ini, place each on a separate line
if setting the Xms and Xmx within Eclipse, put on the same line, one space apart
Ensure the amount of memory that you set aside is a whole number
correct ... -Xms101m
incorrect ... -Xms1.3m

Assigning unreasonable memory requests
Note: The default heap range is from 0 to 64 mb.
Setting the Xms in excess of 64 mb will likely net you:
Incompatible initial and maximum heap sizes specified
Setting either Xms or Xmx in excess of your computer's available RAM will show
Could not reserve enough space for object heap
Could not create the Java virtual machine
Setting either Xms or Xmx in excess of current anticipated use
-Xmx1g is reasonable (though high)
-Xmx512g is not reasonable

As you can see, setting the initial and maximum heap sizes is simple.  This is also implicit as most of this post was about how to properly format the values to which you set requested memory.  If you find anything wrong with these directions or think a particular aspect of clarity should be included, please comment.


Thanks.

Tuesday, April 20, 2010

EdgeRenderer - a model of hidden capability

EdgeSprites { doc / class } show up a great deal in flare; they're used to track, update, and organize rendering of relationships between NodeSprites { doc / class }.  EdgeSprites are rendered (drawn, shaped) based upon their corresponding EdgeRenderer { doc / class}.  The *interesting* thing, like so many other tools available through flare, is that there is no simple way that I've found in the documentation to identify the list of available values for the various methods.

Here is the source: [link]

To explain what I mean, here is an example.

The EdgeRenderer has the following
public properties
caps : String = "null"; The caps style for line rendering. 
instance : EdgeRenderer; Static EdgeRenderer instance. 
joints : String = "null"; The joint style for line rendering. 
miterLimit : int = 3; The miter limit for line rendering.
pixelHinting : Boolean = false; Pixel hinting flag for line rendering.
scaleMode : String = "normal"; Scale mode for line rendering

public methods
render (d : DataSprite ) : void; Renders drawing content for the input DataSprite.

protected methods
setLineStyle ( e:EdgeSprite, g:Graphics ): void{}; Sets the line style for edge rendering.

So I know that scaleMode is a string whose default value is "normal";
what does this mean and what other values are valid?

I've attached EdgeRenderer to the "Classes" portion of this blog.
It may be evaluated here.

Searching through the class, one stumbles across something of interest - "caps" is an attribute of a graphics object; further research indicates this to be used in setting the lineStyle, which specifies a line style that Flash uses for subsequent calls to other Graphics methods (such as lineTo() or drawCircle()) for the object.

Here are valid values for each of those attributes (ref)
caps: Type of caps at the end of lines
values: CapsStyle.NONE; CapsStyle.ROUND; CapsStyle.SQUARE

The other attributes have similar variety and not all make use of lineStyle; caps does ... so does scaleMode, miterLimit, and pixelHinting.

Tuesday, April 13, 2010

NodeLinkTreeLayout - scaling an n-tree from the child up

I have been looking into revamping the Actionscript 3.0 layout named "NodeLinkTreeLayout".  It is simply given a DataList of nodes, identifies the root node, and populates the tree based upon the order of the children of the root, recursively adding children as it paints.

Recently I realized it doesn't paint from the root "down", but paints from the root node's first child "out".
So I'm sharing this and documenting my struggles; perhaps I'll find ways in which my own mental inefficiencies will be documented and captured.  There is certainly an element of hubris in posting at all - I'm hoping that it will be balanced by the fact that these posts will tend to document my failures as well as discoveries I make.

Here are things I've discovered in the class:

1. The edge DataList is only used in setting the rendering of the edges (directed).
It does not affect the order or position of the parent-child relationship in the tree.
I am keeping a separate array of the edges, as well as the edge's inversion state.
The edges are accessible through the visualization.data.edges DataList object.

2. Depth is an unsigned integer.
The depth of the root node is 0.
The depth of a child of the root node is 1.
The depth of a parent of the root node is -1 ... a problem for unsigned integers.

3. Node positions are based upon their effective distance from the first child of the root node.
This node's breadth doesn't really change.
Adding another child to the root node moves the root node the "_dspace".
It also moves the new child node two "_dspaces" away from the first root node.

4. Prelim is distance from the reference wall to where the node "ought" to be.
The reference wall is the wall against which you're painting.
If your using the default orientation (Top to Bottom) this is the left wall
Thus the prelim in this case would be the distance from the left wall.
5. Mod is distance between the prelim and the assumed midpoint of the parent node.
Prelim effectively points to the middle between all children of the parent
Mod is the offset from that middle.

Friday, January 1, 2010

DataTable

package flare.data
{
    /**
     * A data table maintains a collection of data objects, each
     * representing a row of data, and an optional data schema describing
     * the data variables.
     */
    public class DataTable
    {
        /**
         * Creates a new data table instance.
         * @param data an array of tuples, each tuple is a row of data
         * @param schema an optional DataSchema describing the data columns
         */
        public function DataTable(data:Array, schema:DataSchema=null) {
            this.data = data;
            this.schema = schema;
        }
       
        /** A DataSchema describing the data columns of the table. */
        public var schema:DataSchema;
       
        /** An array of data objects, each representing a row of data. */
        public var data:Array;
       
    } // end of class DataTable
}

DataSet

package flare.data
{  
    /**
     * A data set is a collection of data tables.
     */
    public class DataSet
    {
        /**
         * Creates a new DataSet.
         * @param nodes a data table of node data
         * @param edges a data table of edge data (optional, for graphs only)
         */
        public function DataSet(nodes:DataTable, edges:DataTable=null) {
            this.nodes = nodes;
            this.edges = edges;
        }

        /** A DataTable of nodes (or table rows). */
        public var nodes:DataTable = null;
      
        /** A DataTable of edges. */
        public var edges:DataTable = null;

    } // end of class DataSet
}

Classes

flare->data->DataSet 
flare->data->DataTable
flare->vis->data->render->EdgeRenderer 
flare->vis->data->EdgeSprite 
flare->vis->data->NodeSprite

Edge Sprite

package flare.vis.data
{
import flare.vis.data.render.ArrowType;
import flare.vis.data.render.EdgeRenderer;

/**
* Visually represents a connection between two data elements. Examples
* include an edge in a graph structure or a line between points in a line
* chart. EdgeSprites maintain source and target
* properties for accessing the NodeSprites connected by this edge. By
* default, EdgeSprites are drawn using an EdgeRenderer.
* EdgeSprites are typically managed by a Data object.
*/
public class EdgeSprite extends DataSprite
{
// -- Properties ------------------------------------------------------

/** The x-coordinate for the first end point of this edge. */
public var x1:Number;
/** The y-coordinate for the first end point of this edge. */
public var y1:Number;
/** The x-coordinate for the second end point of this edge. */
public var x2:Number;
/** The y-coordinate for the second end point of this edge. */
public var y2:Number;

/** The first, or source, node upon which this edge is incident. */
public var source:NodeSprite;
/** The second, or target, node upon which this edge is incident. */
public var target:NodeSprite;

/** Flag indicating if this edge is directed (true) or undirected
* (false). */
public var directed:Boolean = false;

/** The type of arrow to be used on the edge. Default is Arrows.NONE */
public var arrowType:String = ArrowType.NONE;
/** The width of the arrow head. The default is -1, in which case the
* width is automatically determined based on the arrow height or
* the line width. */
public var arrowWidth:Number = -1;
/** The height of the arrow head. The default is -1, in which case the
* height is automatically determined based on the arrow width or
* the line width. */
public var arrowHeight:Number = -1;


// -- Methods ---------------------------------------------------------

/**
* Creates a new EdgeSprite.
* @param source the source node
* @param target the target node
* @param directed true for a directed edge, false for undirected
*/
public function EdgeSprite(source:NodeSprite=null,
target:NodeSprite=null, directed:Boolean=false)
{
this.source = source;
this.target = target;
this.directed = directed;
_lineColor = 0xffcccccc;
_renderer = EdgeRenderer.instance;
}

/**
* Given a node upon which this edge is incident, return the other
* node connected by this edge.
* @param n a node upon which this edge is incident
* @return the other node
*/
public function other(n:NodeSprite):NodeSprite
{
if (n == source) return target;
if (n == target) return source;
else return null;
}

/**
* Clears the edge, removing references to the edge's nodes.
*/
public function clear():void
{
source = null;
target = null;
}

/** @inheritDoc */
public override function render():void
{
if (source != null) {
x1 = source.x;
y1 = source.y;
}
if (target != null) {
x2 = target.x;
y2 = target.y;
}
super.render();
}

} // end of class EdgeSprite
}

NodeSprite

package flare.vis.data
{
import flare.animate.Transitioner;
import flare.util.Arrays;
import flare.util.Filter;
import flare.util.IEvaluable;
import flare.util.Sort;

/**
* Visually represents a data element, such as a data tuple or graph node.
* By default, NodeSprites are drawn using a .
* NodeSprites are typically managed by a Data object.
*
*

NodeSprites can separately maintain adjacency lists for both a
* general graph structure (managing lists for inlinks and outlinks) and a
* tree structure (managing a list for child links and a parent pointer).
* The graph and tree lists are maintained completely separately to
* maximize flexibility. While the the tree lists are often used to record
* a spanning tree of the general network structure, they can also be used
* to represent a hierarchy completely distinct from a co-existing graph
* structure. Take this into account when iterating over the edges incident
* on this node.

*/
public class NodeSprite extends DataSprite
{
/** Flag indicating inlinks, edges that point to this node. */
public static const IN_LINKS:uint = 1;
/** Flag indicating outlinks, edges that point away from node. */
public static const OUT_LINKS:uint = 2;
/** Flag indicating both inlinks and outlinks. */
public static const GRAPH_LINKS:uint = 3; // IN_LINKS | OUT_LINKS
/** Flag indicating child links in a tree structure. */
public static const CHILD_LINKS:uint = 4;
/** Flag indicating the link to a parent in a tree structure. */
public static const PARENT_LINK:uint = 8;
/** Flag indicating both child and parent links. */
public static const TREE_LINKS:uint = 12; // CHILD_LINKS | PARENT_LINK
/** Flag indicating all links, including graph and tree links. */
public static const ALL_LINKS:uint = 15; // GRAPH_LINKS | TREE_LINKS
/** Flag indicating that a traversal should be performed in reverse. */
public static const REVERSE:uint = 16;

// -- Properties ------------------------------------------------------

private var _parentEdge:EdgeSprite;
private var _idx:int = -1; // node index in parent's array
private var _childEdges:/*EdgeSprite*/Array;
private var _inEdges:/*EdgeSprite*/Array;
private var _outEdges:/*EdgeSprite*/Array;
private var _expanded:Boolean = true;

/** Flag indicating if this node is currently expanded. This flag can
* be used by layout routines to expand/collapse connections. */
public function get expanded():Boolean { return _expanded; }
public function set expanded(b:Boolean):void { _expanded = b; }

/** The edge connecting this node to its parent in a tree structure. */
public function get parentEdge():EdgeSprite { return _parentEdge; }
public function set parentEdge(e:EdgeSprite):void { _parentEdge = e; }

/** The index of this node in its tree parent's child links list. */
public function get parentIndex():int { return _idx; }
public function set parentIndex(i:int):void { _idx = i; }

// -- Node Degree Properties ------------------------------------------

/** The number of child links. */
public function get childDegree():uint { return _childEdges==null ? 0 : _childEdges.length; }
/** The number of inlinks and outlinks. */
public function get degree():uint { return inDegree + outDegree; }
/** The number of inlinks. */
public function get inDegree():uint { return _inEdges==null ? 0 : _inEdges.length; }
/** The number of outlinks. */
public function get outDegree():uint { return _outEdges==null ? 0 : _outEdges.length; }

/** The depth of this node in the tree structure. A value of zero
* indicates that this is a root node or that there is no tree. */
public function get depth():uint {
for (var d:uint=0, p:NodeSprite=parentNode; p!=null; p=p.parentNode, d++);
return d;
}

// -- Node Access Properties ---------------------------

/** The parent of this node in the tree structure. */
public function get parentNode():NodeSprite
{
return _parentEdge == null ? null : _parentEdge.other(this);
}

/** The first child of this node in the tree structure. */
public function get firstChildNode():NodeSprite
{
return childDegree > 0 ? _childEdges[0].other(this) : null;
}

/** The last child of this node in the tree structure. */
public function get lastChildNode():NodeSprite
{
var len:uint = childDegree;
return len > 0 ? _childEdges[len-1].other(this) : null;
}

/** The next sibling of this node in the tree structure. */
public function get nextNode():NodeSprite
{
var p:NodeSprite = parentNode, i:int = _idx+1;
if (p == null || i >= p.childDegree) return null;
return parentNode.getChildNode(i);
}

/** The previous sibling of this node in the tree structure. */
public function get prevNode():NodeSprite
{
var p:NodeSprite = parentNode, i:int = _idx-1;
if (p == null || i < 0) return null;
return parentNode.getChildNode(i);
}

// -- Position Overrides -------------------------------

/** @inheritDoc */
public override function set x(v:Number):void
{
if (x!=v) dirtyEdges();
super.x = v;
}
/** @inheritDoc */
public override function set y(v:Number):void
{
if (y!=v) dirtyEdges();
super.y = v;
}
/** @inheritDoc */
public override function set radius(r:Number):void
{
if (_radius!=r) dirtyEdges();
super.radius = r;
}
/** @inheritDoc */
public override function set angle(a:Number):void
{
if (_angle!=a) dirtyEdges();
super.angle = a;
}

// -- Methods ---------------------------------------------------------

/** Mark all incident edges as dirty. */
private function dirtyEdges():void
{
var e:EdgeSprite;
if (_parentEdge) _parentEdge.dirty();
if (_childEdges) for each (e in _childEdges) { e.dirty(); }
if (_outEdges) for each (e in _outEdges) { e.dirty(); }
if (_inEdges) for each (e in _inEdges) { e.dirty(); }
}

// -- Test Methods -------------------------------------

/**
* Indicates if the input node is connected to this node by an edge.
* @param n the node to check for connection
* @param opt flag indicating which links to check
* @return true if connected, false otherwise
*/
public function isConnected(n:NodeSprite, opt:uint=ALL_LINKS):Boolean
{
return visitNodes(
function(d:NodeSprite):Boolean { return n==d; },
opt);
}

// -- Accessor Methods ---------------------------------

/**
* Gets the child edge at the specified position
* @param i the position of the child edge
* @return the child edge
*/
public function getChildEdge(i:uint):EdgeSprite
{
return _childEdges[i];
}

/**
* Gets the child node at the specified position
* @param i the position of the child node
* @return the child node
*/
public function getChildNode(i:uint):NodeSprite
{
return _childEdges[i].other(this);
}

/**
* Gets the inlink edge at the specified position
* @param i the position of the inlink edge
* @return the inlink edge
*/
public function getInEdge(i:uint):EdgeSprite
{
return _inEdges[i];
}

/**
* Gets the inlink node at the specified position
* @param i the position of the inlink node
* @return the inlink node
*/
public function getInNode(i:uint):NodeSprite
{
return _inEdges[i].source;
}

/**
* Gets the outlink edge at the specified position
* @param i the position of the outlink edge
* @return the outlink edge
*/
public function getOutEdge(i:uint):EdgeSprite
{
return _outEdges[i];
}

/**
* Gets the outlink node at the specified position
* @param i the position of the outlink node
* @return the outlink node
*/
public function getOutNode(i:uint):NodeSprite
{
return _outEdges[i].target;
}

// -- Mutator Methods ----------------------------------

/**
* Adds a child edge to this node.
* @param e the edge to add to the child links list
* @return the index of the added edge in the list
*/
public function addChildEdge(e:EdgeSprite):uint
{
if (_childEdges == null) _childEdges = new Array();
_childEdges.push(e);
return _childEdges.length - 1;
}

/**
* Adds an inlink edge to this node.
* @param e the edge to add to the inlinks list
* @return the index of the added edge in the list
*/
public function addInEdge(e:EdgeSprite):uint
{
if (_inEdges == null) _inEdges = new Array();
_inEdges.push(e);
return _inEdges.length - 1;
}

/**
* Adds an outlink edge to this node.
* @param e the edge to add to the outlinks list
* @return the index of the added edge in the list
*/
public function addOutEdge(e:EdgeSprite):uint
{
if (_outEdges == null) _outEdges = new Array();
_outEdges.push(e);
return _outEdges.length - 1;
}

/**
* Removes all edges incident on this node. Note that this method
* does not update the edges themselves or the other nodes.
*/
public function removeAllEdges():void
{
removeEdges(ALL_LINKS);
}

/**
* Removes all edges of the indicated edge type. Note that this method
* does not update the edges themselves or the other nodes.
* @param type the type of edges to remove. For example, IN_LINKS,
* OUT_LINKS, TREE_LINKS, etc.
*/
public function removeEdges(type:int):void
{
var e:EdgeSprite;
if (type & PARENT_LINK && _parentEdge) {
_parentEdge = null;
}
if (type & CHILD_LINKS && _childEdges) {
while (_childEdges.length > 0) { e=_childEdges.pop(); }
}
if (type & OUT_LINKS && _outEdges) {
while (_outEdges.length > 0) { e=_outEdges.pop(); }
}
if (type & IN_LINKS && _inEdges) {
while (_inEdges.length > 0) { e=_inEdges.pop(); }
}
}

/**
* Removes an edge from the child links list. Note that this method
* does not update the edge itself or the other node.
* @param e the edge to remove
*/
public function removeChildEdge(e:EdgeSprite):void
{
Arrays.remove(_childEdges, e);
}

/**
* Removes an edge from the inlinks list. Note that this method
* does not update the edge itself or the other node.
* @param e the edge to remove
*/
public function removeInEdge(e:EdgeSprite):void
{
Arrays.remove(_inEdges, e);
}

/**
* Removes an edge from the outlinks list. Note that this method
* does not update the edge itself or the other node.
* @param e the edge to remove
*/
public function removeOutEdge(e:EdgeSprite):void
{
Arrays.remove(_outEdges, e);
}

// -- Visitor Methods --------------------------------------------------

/**
* Sorts the order of connected edges according to their properties.
* Each type of edge (in, out, or child) is sorted separately.
* @param opt flag indicating which set(s) of edges should be sorted
* @param sort the sort arguments.
* If a String is provided, the data will be sorted in ascending order
* according to the data field named by the string.
* If an Array is provided, the data will be sorted according to the
* fields in the array. In addition, field names can optionally
* be followed by a boolean value. If true, the data is sorted in
* ascending order (the default). If false, the data is sorted in
* descending order.
*/
public function sortEdgesBy(opt:uint=ALL_LINKS, ...sort):void
{
if (sort.length == 0) return;
if (sort[0] is Array) sort = sort[0];

var s:Function = Sort.$(sort);
if (opt & IN_LINKS && _inEdges != null) _inEdges.sort(s);
if (opt & OUT_LINKS && _outEdges != null) _outEdges.sort(s);
if (opt & CHILD_LINKS && _childEdges != null) {
_childEdges.sort(s);
for (var i:uint=0; i<_childEdges.length; ++i)
_childEdges[i].other(this).parentIndex = i;
}
}

/**
* Visits this node's edges, invoking a function on each visited edge.
* @param f the function to invoke on the edges. If the function
* returns true, the visitation is ended with an early exit.
* @param opt flag indicating which sets of edges should be visited
* @return true if the visitation was interrupted with an early exit
*/
public function visitEdges(f:Function, opt:uint=ALL_LINKS,
filter:*=null):Boolean
{
var ff:Function = Filter.$(filter);
var rev:Boolean = (opt & REVERSE) > 0;
if (opt & IN_LINKS && _inEdges != null) {
if (visitEdgeHelper(f, _inEdges, rev, ff)) return true;
}
if (opt & OUT_LINKS && _outEdges != null) {
if (visitEdgeHelper(f, _outEdges, rev, ff)) return true;
}
if (opt & CHILD_LINKS && _childEdges != null) {
if (visitEdgeHelper(f, _childEdges, rev, ff)) return true;
}
if (opt & PARENT_LINK && _parentEdge != null) {
if ((ff==null || ff(_parentEdge)) && f(_parentEdge))
return true;
}
return false;
}

private function visitEdgeHelper(f:Function, a:Array, r:Boolean,
ff:Function):Boolean
{
var i:uint, n:uint=a.length, v:*;
if (r) {
for (i=n; --i>=0;) {
if ((ff==null || ff(a[i])) && f(a[i]) as Boolean)
return true;
}
} else {
for (i=0; i
if ((ff==null || ff(a[i])) && f(a[i]) as Boolean)
return true;
}
}
return false;
}

/**
* Visits the nodes connected to this node by edges, invoking a
* function on each visited node.
* @param f the function to invoke on the nodes. If the function
* returns true, the visitation is ended with an early exit.
* @param opt flag indicating which sets of edges should be traversed
* @return true if the visitation was interrupted with an early exit
*/
public function visitNodes(f:Function, opt:uint=ALL_LINKS,
filter:*=null):Boolean
{
var ff:Function = Filter.$(filter);
var rev:Boolean = (opt & REVERSE) > 0;
if (opt & IN_LINKS && _inEdges != null) {
if (visitNodeHelper(f, _inEdges, rev, ff)) return true;
}
if (opt & OUT_LINKS && _outEdges != null) {
if (visitNodeHelper(f, _outEdges, rev, ff)) return true;
}
if (opt & CHILD_LINKS && _childEdges != null) {
if (visitNodeHelper(f, _childEdges, rev, ff)) return true;
}
if (opt & PARENT_LINK && _parentEdge != null) {
if ((ff==null||ff(_parentEdge)) && f(_parentEdge.other(this)))
return true;
}
return false;
}

private function visitNodeHelper(f:Function, a:Array, r:Boolean,
ff:Function):Boolean
{
var i:uint, n:uint=a.length, u:NodeSprite;
if (r) {
for (i=n; --i>=0;) {
u = a[i].other(this);
if ((ff==null || ff(u)) && f(u) as Boolean)
return true;
}
} else {
for (i=0; i
u = a[i].other(this);
if ((ff==null || ff(u)) && f(u) as Boolean)
return true;
}
}
return false;
}

/**
* Visits the subtree rooted at this node using a depth first search,
* invoking the input function on each visited node.
* @param f the function to invoke on the nodes. If the function
* returns true, the visitation is ended with an early exit.
* @param preorder if true, nodes are visited in a pre-order traversal;
* if false, they are visited in a post-order traversal
* @return true if the visitation was interrupted with an early exit
*/
public function visitTreeDepthFirst(f:Function, preorder:Boolean=false):Boolean
{
if (preorder && (f(this) as Boolean)) return true;
for (var i:uint = 0; i
if (getChildNode(i).visitTreeDepthFirst(f, preorder))
return true;
}
if (!preorder && (f(this) as Boolean)) return true;
return false;
}

/**
* Visits the subtree rooted at this node using a breadth first search,
* invoking the input function on each visited node.
* @param f the function to invoke on the nodes. If the function
* returns true, the visitation is ended with an early exit.
* @return true if the visitation was interrupted with an early exit
*/
public function visitTreeBreadthFirst(f:Function):Boolean
{
var q:Array = new Array(), x:NodeSprite;

q.push(this);
while (q.length > 0) {
if (f(x=q.shift()) as Boolean) return true;
for (var i:uint = 0; i
q.push(x.getChildNode(i));
}
return false;
}

/**
* Sets property values on edge sprites connected to this node.
* @param vals an object containing the properties and values to set.
* @param opt flag indicating which sets of edges should be traversed
* @param trans a transitioner or time span for updating object values.
* If the input is a transitioner, it will be used to store the
* updated values. If the input is a number, a new Transitioner with
* duration set to the input value will be used. The input is null by
* default, in which case object values are updated immediately.
* @param filter an optional Boolean-valued filter function for
* limiting which items are visited
* @return the transitioner used to update the values
*/
public function setEdgeProperties(vals:Object, opt:uint=ALL_LINKS,
trans:*=null, filter:*=null):Transitioner
{
var t:Transitioner = Transitioner.instance(trans);
for (var name:String in vals) {
var val:* = vals[name];
var v:Function = val is Function ? val as Function
: val is IEvaluable ? IEvaluable(val).eval : null;

visitEdges(function(s:EdgeSprite):void {
t.setValue(s, name, (v!=null ? v(t.$(s)) : val));
}, opt, filter);
}
return t;
}

/**
* Sets property values on node sprites connected to this node.
* @param vals an object containing the properties and values to set.
* @param opt flag indicating which sets of nodes should be traversed
* @param trans a transitioner or time span for updating object values.
* If the input is a transitioner, it will be used to store the
* updated values. If the input is a number, a new Transitioner with
* duration set to the input value will be used. The input is null by
* default, in which case object values are updated immediately.
* @param filter an optional Boolean-valued filter function for
* limiting which items are visited
* @return the transitioner used to update the values
*/
public function setNodeProperties(vals:Object, opt:uint=ALL_LINKS,
trans:*=null, filter:*=null):Transitioner
{
var t:Transitioner = Transitioner.instance(trans);
for (var name:String in vals) {
var val:* = vals[name];
var v:Function = val is Function ? val as Function
: val is IEvaluable ? IEvaluable(val).eval : null;

visitNodes(function(n:NodeSprite):void {
t.setValue(n, name, (v!=null ? v(t.$(n)) : val));
}, opt, filter);
}
return t;
}

} // end of class NodeSprite
}

Edge Renderer

package flare.vis.data.render
{
import flare.util.Geometry;
import flare.util.Shapes;
import flare.vis.data.DataSprite;
import flare.vis.data.EdgeSprite;
import flare.vis.data.NodeSprite;

import flash.display.Graphics;
import flash.geom.Point;
import flash.geom.Rectangle;

/**
* Renderer that draws edges as lines. The EdgeRenderer supports straight
* lines, poly lines, and curves as Bezier or cardinal splines. The type
* of edge drawn is determined from an EdgeSprite's shape
* property and control points found in the points property.
* The line rendering properties are set by the setLineStyle
* method, which can be overridden by subclasses. By default, the line
* rendering settings are determined using default property values set
* on this class (for example, the scaleMode and
* caps properties).
*/
public class EdgeRenderer implements IRenderer
{

private static const ROOT3:Number = Math.sqrt(3);

private static var _instance:EdgeRenderer = new EdgeRenderer();
/** Static EdgeRenderer instance. */
public static function get instance():EdgeRenderer { return _instance; }

/** Pixel hinting flag for line rendering. */
public var pixelHinting:Boolean = false;
/** Scale mode for line rendering (normal by default). */
public var scaleMode:String = "normal";
/** The joint style for line rendering. */
public var joints:String = null;
/** The caps style for line rendering. */
public var caps:String = null;
/** The miter limit for line rendering. */
public var miterLimit:int = 3;

// temporary variables
private var _p:Point = new Point(), _q:Point = new Point();
private var _pts:Array = new Array(20);

/** @inheritDoc */
public function render(d:DataSprite):void
{
var e:EdgeSprite = d as EdgeSprite;
if (e == null) { return; } // TODO: throw exception?
var s:NodeSprite = e.source;
var t:NodeSprite = e.target;
var g:Graphics = e.graphics;

var ctrls:Array = e.points as Array;
var x1:Number = e.x1, y1:Number = e.y1;
var x2:Number = e.x2, y2:Number = e.y2;
var xL:Number = ctrls==null ? x1 : ctrls[ctrls.length-2];
var yL:Number = ctrls==null ? y1 : ctrls[ctrls.length-1];
var dx:Number, dy:Number, dd:Number;

// modify end points as needed to accomodate arrow
if (e.arrowType != ArrowType.NONE)
{
// determine arrow head size
var ah:Number = e.arrowHeight, aw:Number = e.arrowWidth/2;
if (ah < 0 && aw < 0)
aw = 1.5 * e.lineWidth;
if (ah < 0)
{
ah = ROOT3 * aw;
}
else if (aw < 0)
{
aw = ah / ROOT3;
}

// get arrow tip point as intersection of edge with bounding box
if (t==null)
{
_p.x = x2; _p.y = y2;
}
else
{
var r:Rectangle = t.getBounds(t.parent);
if (Geometry.intersectLineRect(xL,yL,x2,y2, r, _p,_q) <= 0)
{
_p.x = x2; _p.y = y2;
}
}

// get unit vector along arrow line
dx = _p.x - xL; dy = _p.y - yL;
dd = Math.sqrt(dx*dx + dy*dy);
dx /= dd; dy /= dd;
// set final point positions
dd = e.lineWidth/2;

// if drawing as lines, offset arrow tip by half the line width
if (e.arrowType == ArrowType.LINES)
{
_p.x -= dd*dx;
_p.y -= dd*dy;
dd += e.lineWidth;
}

// offset the anchor point (the end point for the edge connector)
// so that edge doesn't "overshoot" the arrow head
dd = ah - dd;
x2 = _p.x - dd*dx;
y2 = _p.y - dd*dy;
}

// draw the edge
g.clear(); // clear it out
setLineStyle(e, g); // set the line style

if (e.shape == Shapes.BEZIER && ctrls != null && ctrls.length > 1)
{
if (ctrls.length < 4)
{
g.moveTo(x1, y1);
g.curveTo(ctrls[0], ctrls[1], x2, y2);
}
else
{
Shapes.drawCubic(g, x1, y1, ctrls[0], ctrls[1],
ctrls[2], ctrls[3], x2, y2);
}
}
else if (e.shape == Shapes.CARDINAL)
{
Shapes.consolidate(x1, y1, ctrls, x2, y2, _pts);
Shapes.drawCardinal(g, _pts, 2+ctrls.length/2);
}
else if (e.shape == Shapes.BSPLINE)
{
Shapes.consolidate(x1, y1, ctrls, x2, y2, _pts);
Shapes.drawBSpline(g, _pts, 2+ctrls.length/2);
}
else
{
g.moveTo(x1, y1);
if (ctrls != null)
{
for (var i:uint=0; i
g.lineTo(ctrls[i], ctrls[i+1]);
}
g.lineTo(x2, y2);
}
// draw an arrow
if (e.arrowType != ArrowType.NONE)
{
// get other arrow points
x1 = _p.x - ah*dx + aw*dy; y1 = _p.y - ah*dy - aw*dx;
x2 = _p.x - ah*dx - aw*dy; y2 = _p.y - ah*dy + aw*dx;

if (e.arrowType == ArrowType.TRIANGLE)
{
g.lineStyle();
g.moveTo(_p.x, _p.y);
g.beginFill(e.lineColor, e.lineAlpha);
g.lineTo(x1, y1);
g.lineTo(x2, y2);
g.endFill();
}
else if (e.arrowType == ArrowType.LINES)
{
g.moveTo(x1, y1);
g.lineTo(_p.x, _p.y);
g.lineTo(x2, y2);
}
}
}

/**
* Sets the line style for edge rendering.
* @param e the EdgeSprite to render
* @param g the Graphics context to draw with
*/
protected function setLineStyle(e:EdgeSprite, g:Graphics):void
{
var lineAlpha:Number = e.lineAlpha;
if (lineAlpha == 0) return;

g.lineStyle(e.lineWidth, e.lineColor, lineAlpha,
pixelHinting, scaleMode, caps, joints, miterLimit);
}

} // end of class EdgeRenderer
}