While the nodes tend to be the focus of a graph visualization, the edges that connect them can also be important. Using custom edge renderers can help convey different types of relationships or even the strength of the relationship.
In this example we're going to create an edge renderer which draws a straight line between the nodes. To make it more interesting, we'll vary the thickness of the line according to the edge data's "strength" property. We'll also give it a different color depending on the "type" property.
To do this we'll be overriding an existing edge renderer class rather than creating our own from the ground up. The process here is very similar to creating a custom node renderer. Instead of overriding NodeRenderer, we'll be overriding the EdgeRenderer class.
Here comes the code.
package { import flash.display.Graphics; import flash.display.Sprite; import com.asterisq.constellation.renderers.*; import com.asterisq.graph.*; /** * A custom edge renderer. It draws a line * between its two nodes with a variable * thickness and color. These values vary * with the "thickness" and "type" properties * of the edge. */ public class CustomEdgeRenderer extends EdgeRenderer { // we'll draw the line onto this Sprite private var line_sp:Sprite; /** * Creates a new edge renderer. */ public function CustomEdgeRenderer() { } /** * This is where you put all the code to create * child instances and add them to the display * list. */ override protected function createChildren():void { super.createChildren(); // create an empty sprite and add it to the // display list. we'll draw on it later line_sp = new Sprite(); addChild(line_sp); } /** * This is called when properties change. If the * "thickness" or "type" properties change we * need to redraw. */ override protected function commitProperties():void { super.commitProperties(); // just schedule a call to updateDisplayList invalidateDisplayList(); } /** * This method is called when the renderer needs * to be redrawn. This is where you would position * children or change text labels. */ override protected function updateDisplayList( w:Number, h:Number):void { super.updateDisplayList(w, h); // remember to check that there is data to render if (edge) { // read the thickness and type properties from // the edge data object var strength:Number = edge.data.strength; var type:String = edge.data.type; // set the thickness as half the strength var thickness:Number = strength / 2; // pick a color var color:Number; switch (type) { case 'has_a': color = 0x660000; break; case 'is_a': color = 0x005555; break; } // grab the positions of the tail and head // node renderers var tailPos:Point = tailNodeRenderer.position; var headPos:Point = headNodeRenderer.position; var g:Graphics = line_sp.graphics; g.clear(); g.lineStyle(thickness, color); g.moveTo(tailPos.x, tailPos.y); g.lineTo(headPos.x, headPos.y); } } } }
This is Flex's UIComponent method during which child instances are created and added to the display list. It gets called once when the renderer is instantiated. All we do is create a Sprite instance and add it to the display list.
When the properties of a UIComponent change, it doesn't always need to be redrawn so Flex's component model has a separate method. In our case, if the edge properties change we do need to redraw so we make a quick call to invalidateDisplayList().
This is where we do the actual drawing of the edge renderer. First, we pick up values for the thickness of the line, and the type of relationship we're drawing. Then we choose a color based on the type: sort of a burgundy color for the "has_a" relationship and a muted blue-green for the "is_a" relationship.
Our edge renderer class is ready, so we need to get Constellation to use it. As with the node renderer example, we must first create an edge renderer factory class.
package { import com.asterisq.constellation.renderers.*; import com.asterisq.graph.*; /** * Factory class for custom edge renderers. */ public class CustomEdgeRendererFactory implements IEdgeRendererFactory { public function CustomEdgeRendererFactory() { } /** * This method is called to create a new edge * renderer from a LinkedEdge. */ public function createEdgeRenderer( edge:LinkedEdge):IEdgeRenderer { return new CustomEdgeRenderer(); } public function destroyEdgeRenderer( er:IEdgeRenderer):void { } } }
That was easy. All we need are two methods which are called by Constellation when creating and destroying edge renderers. Now that the factory is ready we just initialize the nodeRendererFactory property of Constellation like this:
var c:FlexConstellation = new Constellation(); c.edgeRendererFactory = new CustomEdgeRendererFactory();
This edge renderer is a simple example of how the visualization's display can be customized. As you can see, the door is wide open in terms of what you can draw. If you want, you can draw dashed lines, curved lines, or a row of dots between the nodes.
It's also possible to create text fields so you can have edge labels. You could even put components in the edge renderer, such as checkboxes or dropdown lists.