InfoVis
A friend asked me to rewrite a piece of code he found and was working with to create a simple info visualizer for a site. The original code came from a book by Jamie MacDonald, and as it was in AS2 I found the text of limited use, though I borrowed the bones of the XML parsing function.
The concept was that a central cluster could be manipulated so that each leaf element of the cluster can be expanded to a secondary cluster and then each leaf of that node cluster can be selected. Though at first I protested, I kept to keeping it linear rather than expanding this to handle multiple nested nodes with the parents receding into 3D space. Thanks to Neil for keeping it simple.
The XML looks like this, but the recursive code should be able to handle more nesting.
<cat catname="Info-Vis"> ... </cat> |
What follows comes from the Main class. After loading the XML parseNodes is called which builds the data into an Object. Then buildNodeset instantiates the first Cluster, which builds all of the data recursively, seen below in Cluster.as. Node_Config is a singleton that is keeping track of a couple of globals including the center screen location and a handle to the main class. These are incomplete code snippets and variables beginning with "_" are class properties.
from Main.as
private function onLoadXML(e:Event):void{ private function parseNodes(xmNode:XML,currentObj:Object){ private function buildNodeset():void{ |
Cluster.as extends the class Node.as which provides the updating for the rotation of the lines.
... public function Node(myDaddy,type) { _myDaddy = myDaddy; _lineColor = Node_Config.NODELINECOLOR; _type = type; _lineSprite = new Sprite(); _gLine = _lineSprite.graphics; addChild(_lineSprite); this.addEventListener(Event.ENTER_FRAME,update,false,0,true); } public function update(e:Event):void{ this.x = _myRadius*Math.cos(_myAngle); this.y = _myRadius*Math.sin(_myAngle); _gLine.clear(); _gLine.lineStyle(2, _lineColor, 1, true); _gLine.lineTo(_myDaddy._myDaddy.x-this.x, _myDaddy._myDaddy.y-this.y); }
|
The following is from Cluster.as.
public function Cluster(myDaddy:Object,clusterName:String, kidsList:Array) { super(myDaddy,"cluster"); _clusterName = clusterName; _Config = Node_Config.getInstance(); var numOfChildren = kidsList.length; _lineColor = Node_Config.CLUSTERLINECOLOR; for(var i:uint=0;i<numOfChildren;i++){ if(kidsList[i].hasOwnProperty("cat_URL")){ //is an end node not a new cluster _kidsNodeArray.push(new EndNode(this,kidsList[i].cat_name, kidsList[i].cat_URL)); }else{ //is a new cluster _kidsNodeArray.push(new Cluster(this,kidsList[i].cat_name, kidsList[i].kidsArray)); } addChild(_kidsNodeArray[i]); _kidsNodeArray[i].alpha = 0; _kidsNodeArray[i].visible = false; } _centerNode = new EndNode(this,_clusterName); addChild(_centerNode); ... |
As you can see Cluster's constructor will call itself to create new Clusters and EndNodes; instantiating _baseNode in buildNodeset builds out the entire dataset. EndNodes are nodes with URLs to link to. This ended up being pretty simple as the XML came with a single cluster with clusters at it's leaves, and the rest were end nodes. I use Greensocks' TweenMax and/or TweenLite extensively. Two of Node's class properties, _myRadius and _myAngle, are manipulated within the base class Node.as for rotation and extension animations.
There is a NodeLabel class that takes care of creating the label. The EndNode class, which also extends Node, has the MouseEvent listener and opens the URL for the labeled end node, after rotation and translation. I apologize that this is not cleaned up and of course it is incomplete.
public function EndNode(myDaddy,theText,theURL = "") { private function onMouseClick(e:MouseEvent):void{ private function showNodeDone():void{ |
"linkdisplay_iframe" is the name of the iframe that the link will open in. The swf is 900x400 scaled down here to 620x276 so it's hard to read. Use this link to see a full size implementation.