Creating a Custom Edge Renderer

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.

Edge Renderer Subclass

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);
    }
  }
}
}

Breaking Down the Edge Renderer

createChildren()

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.

commitProperties()

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().

validateDisplayList()

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.

The Renderer Factory

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();

More Possibilities...

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.