Visualising data with Flare Pt.2

In part one of this guide I ran through a quick and pretty easy way to get a visualisation up and running using the Flare library. I was pretty much writing that as I learnt how to use Flare, and was frustrated as there seemed to be no similar guide out there that just got you up and running easily.

Flare was originally made as a purely actionscript library, so didn’t hook into Flex as cleanly as one might like. In particular it works on Sprites and having to call a render function for each node/edge which is a pretty hand coding way of doing things, and not nearly as clean as following the Spark skinning architecture. So I have started down the path of making Flare work smoothly in Flex and have its nodes and edges be easily skinnable.

It’s not finished yet, not by a long shot, but it does work, at least with some of the layouts, so I thought rather than hold onto it until it’s polished and perfect, I’d share what I’ve done so far, and just continue to release new versions as I work my way through it all and moving it to this new way of handling things.

Caveats:

  • This is really, really not finished. I’ve only got bits working that I so far need in my current project
  • There’s much more clean up and optimisation to be done. There’s whole swags of code that really shouldn’t be needed any more, now that it’s moving away from rendering everything via drawing methods. So if you come across code and think ‘Well, this is just silly, why is this still here?’ or ‘This is really doubling up on functionality’… I know, I know. Just haven’t got there yet.
  • I have left both nodes and edges having renderers at the moment, mostly just because so many parts of flare expects them to be there… Nodes probably don’t need anything there, edges I’m actually still using as ‘drawing’ lines compared to trying to do them as a skin is probably the smarter way.
  • Both nodes and edges have skins. Nodes are generally the thing you’re going to want to skin, as that’s the visually important bits. You can put stuff in an edge skin and it’ll display on the edge along with the drawn line, which could be useful for some things.
  • I constantly flip between spelling visualisation with an s vs a z… sorry about that.
  • Did I mention this is NOT FINISHED? I did? Excellent…

 

Basic usage

Ok, so here’s how you would usually use this Flare library in a Flex project.

<s:Application creationComplete="buildTree(event)" >
	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;

			protected var dataArr:Array = [{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'}
			];

			protected function buildTree(event:FlexEvent):void
			{
				sourceVis.buildData(dataArr);
			}
		]]>
	</fx:Script>

	<datavis:SourceVisualisation id="sourceVis" width="{width}" height="{height}" />

</s:Application>

I’m continuing with the same example from part one, being a source tree. And all we have here is an application (I’ve stripped out all the xmlns entries and other guff that you’ll already know about from there for simplicity) which has an array of data we want to display and the visualisation. When the application finishes creating itself it calls the visualisation buildData function, passing it the array. And that’s it at this level, nice easy mxml markup as expected for a Flex application. The data and triggering the build of the data could all exist within the visualisation itself if you wanted. Then the application would be very simple, but I want the data to be fed to a visualisation to work with, so you can feed it anything (of the right form).

The SourceVisualisation class is an extension of my FlareVisualization (see, I told you I kept flipping s/z) class, which is actually an extension and modification of one that came with the Flare library. It’s basically a flex wrapper around the visualization class which allows you to define the skins/renderers etc. to do with how the visualization looks.

There’s not much to the custom SourceVisualisation class:

public class SourceVisualisation extends FlareVisualization
	{
		/**
		 * Set some defaults for this Vis...
		 * @param data
		 *
		 */
		public function SourceVisualisation(data:Data=null){
			nodeSkin=SourceIcon;
			operators=[new IndentedTreeLayout()];
			controls=[new ExpandControl()];
			super(data);
		}

 

Is all there is to define how this looks. It defines the nodeSkin, and then sets an operator as being the layout to use. I’ve considered having layout be a separate attribute on FlareVisualization as it’s always something you want to set, and will probably do so. But for now you just put your desired one as an operator. Ditto with controls, where I’ve added an ExpandControl, which handles clicking on nodes to expand and contract levels of the tree.

The only other thing you’ll need to put in your class is an override of the buildData function to handle data of the form you expect in. So for our example it’s:

override public function buildData(arr:Array):Data{
			//Will probably move this to an 'createData' function that
			//passes all the configured vars so you don't have to think about
			//it... and will set up a root if your vis is of type 'tree'
			//bit more to add for all this to work.
			var d:Data = new Data(false,nodeSkin,edgeSkin,'description');
			var root:NodeMode = d.createRootNode();
			root.data.type = 'root';
			root.data.description = 'The entire World!';

			//Keyed lookup for easy edge addition in step 2
			var nodeLookup:Object = {};
			var row:Object;
			var ns:NodeMode;				

			//Step 1: Add all rows of data;
			for each(row in arr){
				ns = d.addNode(row);
				nodeLookup[row.id] = ns;
			}

			//Step 2: Add edges
			for each(ns in d.nodes){
				if(ns.data.type == 'host'){
					d.addEdgeFor(root,ns);
				}
				if(ns.data.hasOwnProperty('host')){
					d.addEdgeFor(nodeLookup[ns.data.host],ns);
				}
				else if(ns.data.hasOwnProperty('dir')){
					d.addEdgeFor(nodeLookup[ns.data.dir],ns);
				}
			}
			this.data = d;
			return d;
		}

 

So, stepping through this we create a new Flare Data Object passing it a bunch of config options which are new things I’ve added. Originally I thought that an object called ‘Data’ should have no knowledge that what it’s handling has a visual representation, but as a class it really does just handle data for visualisations, so I decided it was ok for it to be handed what skins to use for the nodes and the edges, seeing as it instantiates them. The final param is optional, and says that nodes should set their toolTip attribute to be the value within the ‘description’ field of the data. This then activates the default Flex tooltip, so you can skin that however you wish. The other method of providing tooltips is to use the flare tooltip process which I started trying to bash into submission, but came unstuck with some typing of objects and so stopped midway… so if you wanted to still use their process with this library you’ll find I’ve broken it :-/

The edgeSkin being passed to the Data constructor up there is set by default as being an empty skin, it just needs to exist as both edges and nodes extend SkinnableComponent and so need a skin. The NodeSkin for this example is pretty simple:

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
		xmlns:s="library://ns.adobe.com/flex/spark"
		xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:util="com.doubleiq.dove.util.*"
		creationComplete="setup(event)">
	<fx:Metadata>
		[HostComponent("flare.vis.data.NodeMode")]
	</fx:Metadata>

	<s:states>
		<s:State name="dirty"/>
		<s:State name="clean"/>
	</s:states>		

	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;
			private var imageLookup:Object = {host:"assets/images/visualisations/computer.png",db:"assets/images/visualisations/database.png",
											  dir:"assets/images/visualisations/folder.png",file:"assets/images/visualisations/document_index.png",
											  root:"assets/images/visualisations/world.png"};

			[Bindable]
			protected var imageSrc:String;

			protected function setup(event:FlexEvent):void
			{
				hostComponent.addEventListener('updateData', updateIcon);
				updateIcon();//First go
			}

			protected function updateIcon(e:FlexEvent=null):void{
				if(hostComponent.data){
					imageSrc = imageLookup[hostComponent.data.type];
					txt.text = hostComponent.data.name;
					hostComponent.height = 50;
					hostComponent.width = 40;
				}
			}

		]]>
	</fx:Script>
	<s:VGroup gap="5" horizontalAlign="center" x="-15" y="-15">
		<s:Image id="img" source="{imageSrc}"/>
		<s:Label id="txt" styleName="text"/>
	</s:VGroup>

</s:Skin>

It’s just an image and a Label, which get updated whenever the data updates. Two things about this:

  • The skin states (dirty/clean) are not being used and are not what nodes/edges will end up having. They’ll be more like selectedExpanded, selectedNotExpanded and so on, to drive actual visual differences to the skin, but I’m not triggering any state changes as yet.
  • This skin is setting the hostComponent size args. This is crappy I know, but I haven’t got to the bottom yet of why the hostComponent is not sizing to the skin, or whether it should be. But you have to have the hostComponent (NodeMode) sizes set, otherwise the layouts don’t work. Another way of handling this is to have a default NodeRenderer that the render event takes the .skin.height/width args and sizes itself using these. Then you wouldn’t have to worry about doing it in skin… but I had an issue that the skin wasn’t resizing based on its content and so had to manually set the sizes anyway, so why not just do it directly to the host from the skin and remove the need for a renderer at all?

 

The end result of this is a visualization that displays as:

And that’s it. The basics of using this are:

  • Define a skin for your nodes
  • Define a method to convert a source array into a node/edge structure you need

That’s it.

I think it’s shaping up as a nice and easy to use flex component, but do stress yet again, that it’s not finished as yet 🙂 I hope it leads you down the path of nice flare visualisations within your Flex apps though.

More to come as I polish this thing up. Please do comment with any issues you have which you think aren’t purely from me not getting to an area yet, but moreso with my approach.

Here is a link to the new flare project: Flare Flex library v 0.1

Bookmark the permalink.