Implementing a Custom Node Renderer

There are a variety of ways that you might want to display a node: shapes, text, images, or even little charts or video clips. Fortunately, creating your own NodeRenderer is a relatively simple process.

Node Renderer Subclass

To create a custom node renderer you subclass NodeRenderer. The main function to override is the updateDisplayList() method. There are some additional details to consider so we'll have a look at an example.

This node renderer class draws a red circle at the node's position. The radius of the red circle is set by the node's data, specifically, the "radius" property.

The validation code is based heavily on the Flex components so if you're familiar with those this will be a breeze.

Don't get scared by the huge code listing; we'll break it down in more detail below.

package { import flash.display.Graphics; import flash.display.Sprite; import com.asterisq.constellation.renderers.*; import com.asterisq.graph.*; /** * A custom node renderer. This one draws a nice, * red circle whose radius is determined by the * node's data. */ public class CustomNodeRenderer extends NodeRenderer { private var circle_sp:Sprite; /** * Creates a new node renderer. */ public function CustomNodeRenderer() { } /** * This method gets called when the renderer is * first created. This is where you should do * initial setup like creating child instances. */ override protected function createChildren():void { super.createChildren(); // create an empty sprite and add it to the // display list. we'll draw on it later circle_sp = new Sprite(); addChild(circle_sp); } /** * 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( unscaledWidth:Number, unscaledHeight:Number):void { super.updateDisplayList(unscaledWidth, unscaledHeight); // always check that there is data to render if (node) { // read the radius from the node data var radius:Number = node.data.radius; // draw a red circle with the radius given // in the node data var g:Graphics = circle_sp.graphics; g.clear(); g.beginFill(0xff0000); g.drawCircle(0, 0, radius); g.endFill(); } } } }

Breaking Down the Node Renderer

createChildren()

First off, we get things setup inside the createChildren() method. This method has the same function as with the Flex components. It's only called once at the beginning of the node renderer's life. For this example, we simply create a new Sprite on which we'll draw our red circle.

updateDisplayList()

Next up is the updateDisplayList() method. This gets called every time some property of the node renderer changes. In our case we only care about two properties, the position and the radius, but we'll get into that later.

In the updateDisplayList() method we check that there is a node to render and then redraw our circle with the given radius.

The Renderer Factory

Now that we've created our node renderer class, we need to tell Constellation about it. This is done by setting the nodeRendererFactory. But first we need to create the factory class.

package { import com.asterisq.constellation.renderers.*; import com.asterisq.graph.*; /** * Factory class for custom node renderers. */ public class CustomNodeRendererFactory implements INodeRendererFactory { public function CustomNodeRendererFactory() { } /** * This method is called to create a new node * renderer from a LinkedNode. */ public function createNodeRenderer( node:LinkedNode):INodeRenderer { return new CustomNodeRenderer(); } public function destroyNodeRenderer( nr:INodeRenderer):void { } } }

As you can see, there's not much to the factory class. You need to implement INodeRendererFactory by defining two public methods, createNodeRenderer() and destroyNodeRenderer().

Whenever Constellation wants to create a new node renderer, it will call createNodeRenderer(). Whenever one is being removed, it will call destroyNodeRenderer().

Once you've got the factory class ready, you can set that nodeRendererFactory property like this:

var c:Constellation = new Constellation(); c.nodeRendererFactory = new CustomRendererFactory();

From Here...

There are a couple things to note with the way this was designed. The use of a factory class means that you can respond to creation and destruction of node renderers and even implement pooling to reduce processing.

Also, since the node instance that will be rendered is passed into the createNodeRenderer() method, it's possible to return different node renderers for different nodes. You might test whether the node type is a person or a company, for example, and return a different node renderer for each.

The possibilities are endless!