I have a new version of FlareForFlex (Yeah, that’s my name for it now 🙂 ) to release. This release is much more usable than the last, and is actually being used in a production environment now. Again, it’s got plenty of bugs, only has the bits working that I’ve actually needed in the project I’m working on, but I think it’s a pretty useful extension to the original flare library for Actionscript.
Let’s just dive into some examples shall we?
Simple Example
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" xmlns:vis="vis.*"> <fx:Script> <![CDATA[ import mx.collections.ArrayCollection; [Bindable] protected var dataArr:ArrayCollection = new ArrayCollection([{name:'Host 1',type:'host',id:'1',description:'I am a mighty Host'}, {name:'Host 2',type:'host',id:'2',description:'I am a lesser host'}, {name:'Database 1',type:'db',id:'3',host:'1',description:'Database of doom'}, {name:'Database 2',type:'db',id:'4',host:'1',description:'Databse of fairies'}, {name:'Folder 1',type:'dir',id:'5',host:'2',description:'Directory of scary things'}, {name:'File 1',type:'file',id:'6',dir:'5',description:'I am a thing of terror'}, {name:'File 2',type:'file',id:'7',dir:'5',description:'I am a thing of mild annoyance'} ]); ]]> </fx:Script> <vis:DemoVisualisation id="demoVis" rawData="{dataArr}" left="20" top="10" width="{width}" height="{height}" autoResize="false"/> </s:Application>
This Flex application just defines some data and then a visualisation to display it. It results in the same visualisation we had in previous example posts here on the site (below is just an image, rest are actual swfs).
This code that sits in the DemoVisualisation just defines how to process the dataset, and what visual parameters we have. So, firstly you add the config settings into the constructor:
public function DemoVisualisation(data:Data=null){ super(data); //Set the skin for the nodes nodeSkin=TreeIcon; //Set the layout layout = new IndentedTreeLayout(30,5); //Set the field to pull tooltip text from tooltipField = 'description'; //Turn off mouse panning and zooming for the tree layout panZoomEnabled = false; }
So, this sets the skin for the nodes for this visualisation (much the same as previous examples, won’t go into it here, it’s in the demo code included here), then sets the layout to be an indented tree with some sizing args. A quick and easy way to get useful tooltips on your visualisation is to set the tooltipField. This causes tooltip text to be pulled from the node’s data object, specifically from the attribute with the name you specify here. Then we turn off mouse panning and zooming, seeing as that makes little sense for a tree style of layout.
The only other thing you usually do for a visualisation is to create your buildData function to handle your specific dataset. Visualisations don’t have built in functions to handle your dataset directly, as the forms that data can take are many and varied. So I’ve made there be just one function you must write to turn your dataset (expected to be an ArrayCollection) into a collection of Nodes and Edges, and then everything else is automated.
The buildData included with this demo has all the trappings you’d usually want, including handling data updates vs complete refreshes, as such is relatively long. Your processing may be a lot shorter:
override public function buildData(arr:ArrayCollection=null, fullRefresh:Boolean=true):Data{ var d:Data; var updateRequired:Boolean = false; var row:Object; var nm:NodeMode; var unMatchedNodes:Object = {}; var root:NodeMode; var uid:String; if(fullRefresh){ d = new Data(false,nodeSkin,edgeSkin,tooltipField); root = d.createRootNode(); root.data.type = 'root'; root.data.description = 'The entire World!'; nodeLookup = {}; updateRequired = true; } else{ d = this.data as Data; root = d.root; } for each(nm in nodeLookup){ unMatchedNodes[nm.data.type+'_'+nm.data.id] = nm; } //Step 1: Add all rows of data; for each(row in arr){ uid = row.type+'_'+row.id; delete unMatchedNodes[uid]; //If new row, just add if(!nodeLookup[uid]){ nm = d.addNode(row); nm.dodgeIconIncoming = false; nodeLookup[uid] = nm; updateRequired = true; }//Else update data, leave other node info else{ nm = nodeLookup[uid]; nm.data = row; } } var em:EdgeMode; //Step 2: Add edges for each(nm in d.nodes){ if(nm.data.type == ObjectTypes.HOST){ if(!d.findEdgeFor(root,nm)){ em = addEdgeFor(d,root,nm); } } else if(nm.data.hasOwnProperty('host')){ if(nodeLookup[ObjectTypes.HOST+'_'+nm.data.host]){ if(!d.findEdgeFor(nodeLookup[ObjectTypes.HOST+'_'+nm.data.host],nm)){ em = addEdgeFor(d,nodeLookup[ObjectTypes.HOST+'_'+nm.data.host],nm); } } else{ d.removeNode(nm); } } else if(nm.data.hasOwnProperty('dir')){ if(nodeLookup[ObjectTypes.DIR+'_'+nm.data.dir]){ if(!d.findEdgeFor(nodeLookup[ObjectTypes.DIR+'_'+nm.data.dir],nm)){ em = addEdgeFor(d,nodeLookup[ObjectTypes.DIR+'_'+nm.data.dir],nm); } } else{ d.removeNode(nm); } } else{ //trace('NO EDGE FOR '+ns.data.name); } } //If we have any nodes that have been deleted for each(nm in unMatchedNodes){ //trace('Unmatched node, deleting: '+ns.data.name); updateRequired = true; delete nodeLookup[nm.data.type+'_'+nm.data.id]; d.removeNode(nm); } this.data = d; return d; }
The basic premise is that you create all your nodes based on your data, and then you add edges between them based on whatever rules your dataset has for linking objects. In this case it’s files to folders, folders to hosts, and databases to hosts. Yours may be supertype/subtype relationships in a class diagram, or next process in a data flow diagram.
Switching layouts
All of this, however, hasn’t shown anything new over the previous posts I did. What about if we want a visualisation that you can switch the layout type on the fly? To do that, just add a few lines to your constructor:
public function DemoVisualisation(data:Data=null) { super(data); //Set the skin for the nodes nodeSkin=TreeIcon; //Create a hash of possible layouts for this vis. addLayout('nodeLink',new NodeLinkTreeLayout()) addLayout('radial',new RadialTreeLayout(150)); addLayout('tree',new IndentedTreeLayout(30,5)); //Set the initial layout changeLayout('tree'); //Set the field to pull tooltip text from tooltipField = 'description'; }
Now we have three available layouts for this visualisation, and we can switch them by calling changeLayout and passing the string name of that layout. So now the main part of our application becomes:
<s:VGroup width="100%" height="100%" gap="0"> <vis:DemoVisualisation id="demoVis" rawData="{dataArr}" left="20" top="10" width="{width}" height="{height-buttons.height}" autoResize="false"/> <s:HGroup id="buttons"> <s:Label text="Layout : "/> <s:Button label="Tree" click="demoVis.changeLayout('tree')"/> <s:Button label="Node Link" click="demoVis.changeLayout('nodeLink')"/> <s:Button label="Radial" click="demoVis.changeLayout('radial')"/> </s:HGroup> </s:VGroup>
Which results in an application like the following (below is an actual swf to play with)
Styling edges
Something that came up a fair bit while creating the product I’ve been working on was the need to have diagram edges styled differently dependent on what types of nodes they were linking between. After doing this manually a few times within the updateData function I felt there needed to be an easier way. To do this, I added the edgeStyling variable to FlareVisualization which you use as such (add this to the constructor with the other config args):
edgeStyling = { '*:*':{endShapeType:LineShapeType.FILLEDCIRCLE,endShapeWidth:5,lineColor:0xff6666ff}, 'host:db':{dashedLine:false,lineColor:0xffff6666}, 'dir:file':{dashedLine:true,dashLength:2,dashGapLength:2, endShapeType:LineShapeType.NONE,lineColor:0xFF66FF66} }
The format is fromNodeType : toNodeType : {EdgeMode variable values}. It also accepts asterisks as wildcards, so the *:* above is the fall back styling for all edges, with the more specific types then overriding where applicable. You can use asterisks on just one side as well, so *:db would be any edge leading to a node of type db.
This results in a diagram that looks like this:
This is really just scratching the surface of the new functionality that I’ve added to flare. The main ideal was to make it play nice in the Spark skinning world, so now nodes and edges can both have skins (as NodeModes and EdgeModes are skinnable components), and that makes it much easier to make diagrams look different per purpose just by applying some different skins and some styling variables.
Again, most of the same caveats apply as with the previous release: It’s not finished, it has many bugs, it has areas I haven’t even looked at, I may have broken some functionality which you really require and so on… but I find it very handy now, and I hope you do to. I’ve got a reasonable amount of documentation through the code, but that too isn’t really complete. So if you do use it, and have difficulty working out what to do, just drop a message here. I do plan to continue to polish it, and properly document it with ASDocs down the track. Also, thanks again to the original Flare team who created the original library which this is still about 95% comprised of.
As it stands now I give you the library and the demo flex application used for these examples:
FlareForFlex Demo Flex Project