There are many solutions for this out there but I haven’t found any of them for free or as Open Source, so here we are going to learn how to make one from scratch. This version is written in Action Script 3, and it is part of the FlexTutsLibrary. If you want to know what the final product of this tutorial looks like, see it here: view Demo.
To get the FlexTutsLibrary for your projects go to the FlexTuts Library section and download the latest release.
This may be a very basic tutorial for many of you; if so, you are free to skip it and find one that make you feel you are, in fact, learning something :)
In order to create a visual control I think it is important to have an image where you can analyze the behavior, here is my first visualization for this control.

As you see it's not much impressive but good enough to analize behavior, from the image we can conclude and/or assume:
Conclusions
Assumptions
Now we need to think on what this control can do.
Behavior
The node will have the distribution shown in the image

A node is composed by the following visible elements:
You may add more elements according to your needs.
In this step we will create and organize the library project and a flex application project to test the library, from now on I'll assume that you have installed the Flex Builder 3.
Creating the library project: FlexTutsLibrary
To create a library project in Flex Builder 3 go to File>New>Flex Library Project. A window will be shown. Then fill the Project name field with 'FlexTutsLibrary' or the name you want to give to your library and click Finish at the bottom.
Creating the flex application: Organigram
Go to File>New>Flex Project. In the window fill the Project name field with 'Organigram' or the name you want to give to your application project, then click Finish at the bottom.
Creating class files: Node and Organigram
To create the Node class go to File>New>ActionScript Class, in the window check if the Project field says 'FlexTutsLibrary'; if not, you can change it by using the Browse button at the right then fill the Package name field with 'ft.controls' or use your own name for the package, in the Name field write 'Node', I recomend you not to use other name but you can use another if you want, in the Superclass field write 'mx.containers.Canvas'. Click Finish.
To create the Organigram you have to do the same you did to create ne Node class but in the Name field write Organigram instead.
In the next image you can see how your project should looks, at this point your project does not have resources folder that appears in the FlexTutsLibrary project, we will talk about it later. Another difference you may find are the icons. I am using a CVS repository to develop the project and the icons changed when I began to use CVS. It's not a problem not to use a CVS repository but if you have a CVS server I recommend you to use it.

Before reading this step, you have to download the source code of this control from the FlexTuts Library section, that way it will be easier to understand what we are doing.
Calculating the size (width and height) of a node
The following image will help us calculate the total width and height

from the image we can say:
nodeWidth = border + picture.width + space + title.width + border nodeHeight = border + max ( picture.height , title.height + description.height ) + border
Overriding functions in the Node class
When you make a visual control you must at least inherit from the UIComponent of the mx.core package, in this case we made it inherit from Canvas just to avoid some events implementation like the "click".
There are some function you must override for your control to work properly and they are:
createChildren : will be called by the system when the control is created
commitProperties : will be called by the system when invalidateProperties is called
measure : will be called by the system when needs to calculate you control's size
updateDisplayList : will be called by the system when its time to place all controls insite yours.
In the createChildren function we create the objects that will be used in our Node control.
private var thumbnail:Image;
private var title:Text;
private var description:Text;
// Implement createChildren function.
override protected function createChildren():void {
super.createChildren();
if(!thumbnail) { // validate if the thumbnail object was created before
thumbnail = new Image(); // creates an image object
thumbnail.explicitHeight = 60; // asigning a default value
thumbnail.explicitWidth = 54; // asigning a default value
thumbnail.setStyle("borderStyle", "solid");
addChild(thumbnail);
}
if(!title) { // validate if the title object was created before
title = new Text(); // creates a text object
title.explicitWidth = 100; // asigning a default value
title.explicitHeight = 20; // asigning a default value
title.selectable = false;
title.setStyle("color",0xFFFFFF);
title.setStyle("fontWeight","bold");
addChild(title);
}
if(!description){ // validate if the description object was created before
description = new Text(); // creates a text object
description.explicitWidth = 100; // asigning a default value
description.explicitHeight = 40; // asigning a default value
description.selectable = false;
description.setStyle("fontWeight","bold");
addChild(description);
}
this.addEventListener("click", handleEventClick);
}In the commitProperties function we assign values from the temporary variables to the controls created before
// Implement commitProperties function.
override protected function commitProperties():void{
super.commitProperties();
if(_thumbnailChanged){
_thumbnailChanged = false;
thumbnail.source = _thumbSource;
}
if(_descriptionChanged){
_descriptionChanged = false;
description.text = _descText;
}
if(_titleChanged){
_titleChanged = false;
title.text = _titleText;
}
}In the measure function we calculate the control size, here we will use the formula we got from the image a the top.
// Implement measure() function.
override protected function measure():void{
super.measure();
var titleWidth:Number;
var descWidth:Number;
var thumbWidth:Number = thumbnail.getExplicitOrMeasuredWidth();
titleWidth = descWidth = Math.max( title.getExplicitOrMeasuredWidth(), description.getExplicitOrMeasuredWidth());
title.measuredWidth = titleWidth;
description.measuredWidth = descWidth;
var titleHeight:Number = title.getExplicitOrMeasuredHeight();
var descHeight:Number = description.getExplicitOrMeasuredHeight();
var thumbHeight:Number = thumbnail.getExplicitOrMeasuredHeight();
this.measuredWidth = this.measuredMinWidth = border + thumbWidth + space + titleWidth + border;
this.measuredHeight = this.measuredMinHeight = Math.max(titleHeight + descHeight, thumbHeight) + 2 * border;
}
In the updateDisplayList function we place the controls in the position they should go.
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var titleWidth:Number;
var descWidth:Number;
var thumbWidth:Number = thumbnail.getExplicitOrMeasuredWidth();
titleWidth = descWidth = Math.max( title.getExplicitOrMeasuredWidth(), description.getExplicitOrMeasuredWidth());
var titleHeight:Number = title.getExplicitOrMeasuredHeight();
var descHeight:Number = description.getExplicitOrMeasuredHeight();
var thumbHeight:Number = thumbnail.getExplicitOrMeasuredHeight();
// Set actual size of each child component
// Set thumbnail actual size
thumbnail.setActualSize(thumbWidth, thumbHeight);
// Set title actual size
title.setActualSize(titleWidth, titleHeight);
// Set description actual size
description.setActualSize(descWidth, descHeight);
// Placing thumbnail
thumbnail.move(border, border);
// Placing title
title.move(border + thumbWidth + space, border);
// Placing description
description.move(border + thumbWidth + space, border + titleHeight);
} In the previous step I left an important implementation, it is important because every class visual or not must have it, it is called the constructor and it has the same as the class that is being implemented, in our case we have the following code for the constructor.
// Implement class constructor
public function Node(): void {
super(); // always call the superclass constructor in aour case Canvas
setStyle("backgroundImage", normalSkin); // Setting up the background image
setStyle("backgroundSize", "100%"); // adjusting background image to the size of our control
setStyle("backgroundAlpha", 0.8); // setting transparency for background image
var fadeIn:Fade = new Fade(); // Effect for for rollover
fadeIn.alphaFrom = 1.0;
fadeIn.alphaTo = 0.7;
fadeIn.duration = 500;
var fadeOut:Fade = new Fade(); / Effect for rolout
fadeOut.alphaFrom = 0.7;
fadeOut.alphaTo = 1.0;
fadeOut.duration = 500;
// Setting up effects
setStyle("rollOverEffect",fadeIn);
setStyle("rollOutEffect",fadeOut);
}
Now, from the step 1 we conclude that each node has one parent and it may have children, to implement this we will declare a parent variable of the type Node and an array for the children, also we need to implement an addChild method to let our control create a new node and add it as a child of the current node.
public var parentNode:Node;
public var childNodes:Array;
// Implementing parents, childs association
public function addChildNode(child:Node):void{
if(!childNodes){
childNodes = [child];
}
else {
childNodes.push(child);
}
child.parentNode = this;
}
Additionally from behavior in step 1 we conclude that we need a status variable for the selection, we will declare selected as boolean to do this and we will change it when the control is clicked, a function to deselect the node is also needed.
public var selected:Boolean = false;
// Event handlers
private function handleEventClick(objEvent:MouseEvent):void{
// find the root node, we will deselect all the nodes and then set this instance as selected.
var rootNode:Node = this;
while(rootNode.parentNode){
rootNode = rootNode.parentNode;
}
deselectNodes(rootNode);
this.selected = true;
this.setStyle("backgroundImage", activeSkin);
invalidateDisplayList();
dispatchEvent(new Event("nodeSelected") );
}
// deselect the node and its children
private function deselectNodes(node:Node):void{
node.selected = false;
node.setStyle("backgroundImage", normalSkin);
node.invalidateDisplayList();
if(node.childNodes){
for( var iNode:int = 0; iNode < node.childNodes.length; iNode++){
deselectNodes(node.childNodes[iNode] as Node);
}
}
}
The Node class was simple, the Organigram is not going to be different, as we did before we need to implement a constructor, override some functions and implement some additional functions.
Calculating the size (width and height) of the organigram
Let's check the picture

To calculate the width we have to find all the nodes without children in the picture those nodes are numbered from 1 to 9, in general we will use N as the number of nodes without children, so we have the next formula for the minimun width:
organigram.width = hSpace + N * node.width + ( N - 1 ) * hSpace + hSpace
Calculating the height is easier, in the image we see 5 levels, we will use L to denote the number of level:
organigram.height = vSpace + L * node.height + ( L - 1 ) * vSpace + vSpace
Implementing the contructor
// Implement the constructor
public function Organigram(){
super();
}
Overriding functions in the Organigram class
In this case the createChildren will not do anything special, just call the commitProperties super class method. We will create the children controls later.
// Implement createChildren function
override protected function createChildren():void{
super.createChildren();
}
The same occurs with the commitProperties function
// Implement commitProperties function.
override protected function commitProperties():void{
super.commitProperties();
}
In the measure function we need to calculate the size of our control, for this we will create two additional functions that will give us the number of levels (countLevels) and the number of nodes without children (countNodesWithoutChilden).
// Implement measure function.
override protected function measure():void{
super.measure();
if(rootNode){
// Use new functions to calculate with and height
var nLevels:uint = countLevels(rootNode);
var nFinalNodes:uint = countNodesWithoutChildren(rootNode);
// Calculate with ( we assume that all nodes have the same with )
var nodeWidth:Number = rootNode.width;
var compWidth:Number = nFinalNodes * nodeWidth + (nFinalNodes + 1) * _hSpace ;
// Calculate height
var nodeHeight:Number = rootNode.height;
var compHeight:Number = nLevels * nodeHeight + (nLevels + 1 ) * _vSpace ;
this.measuredWidth = this.measuredMinWidth = compWidth;
this.measuredHeight = this.measuredMinHeight = compHeight;
}
}The countLevel function looks for the highest level of all the children for a given node.
// Calculates how many level have this tree
private function countLevels(node:Node, level:uint = 1):uint{
if(node)
{
if(!node.childNodes){
return level;
}
else{
var maxLevel:uint = level;
for( var iNode:Number = 0; iNode < node.childNodes.length ; iNode++) {
var childNode:Node = node.childNodes[iNode] as Node;
maxLevel = Math.max(maxLevel, countLevels(childNode, level + 1));
}
return maxLevel;
}
}
else{
return 0;
}
}The countNodesWithoutChildren is also recursive and returns 1 for any node withoutchildren, if the node has children it evaluates every children and accumulates the returned values.
// Calculates how many nodes without children have this tree
private function countNodesWithoutChildren(node:Node):uint{
if(node){
if(!node.childNodes){
return 1;
}
else{
var count:uint = 0;
for( var iNode:Number = 0; iNode < node.childNodes.length ; iNode++){
var childNode:Node = node.childNodes[iNode] as Node;
count += countNodesWithoutChildren(childNode);
}
return count;
}
}
else{
return 0;
}
}In the next step we will see how to place every node in the correct position.
In this final step we will place every node in it's correct position, from the previous step we know that the width of the entire control depends on the number of nodes without children and the number of level, so, we can easily place the nodes without children, its position is given by the up left corner position (x, y).
For every node without children we have:
x = (n - 1) * nodeWidth + (n - 1) * hSpace + hSpace; // n = number of node without children y = (l - 1) * nodeHeight + (l - 1) * vSpace + vSpace; // l = number of level
the previous formulas can be writen like this:
x = (n - 1) * (nodeWidth + hSpace) + hSpace; // n = number of node without children y = (l - 1) * (nodeHeight + vSpace) + vSpace; // l = number of level
After placing nodes without children it is very easy to place parent nodes, we just have to recognize that a parent node its at a lower level and in the middle of its first and last children.
To analyze nodes of the entire tree, we will call a recursive function and pass an array reference to let u count how many nodes without children we find.
We have to do this on the updateDisplayList function, let's see how to implemented it.
// Implement updateDisplayList function
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
super.updateDisplayList(unscaledWidth,unscaledHeight);
if(rootNode){
// We will calculate the order in wich the nodes should be placed.
var nodesWOC:Array = [0];
var posRoot:Array = placeNode(rootNode,1, nodesWOC);
drawPathLines(rootNode);
}
else{
graphics.clear();
}
}
The placeNode function will place all the modes begining with the nodes without children.
private function placeNode(node:Node, level:uint, nodesWOC:Array):Array{
var nodeHeight:Number = node.height;
var nodeWidth:Number = node.width;
if(!node.childNodes){
// Placing a node without children
nodesWOC[0] = (nodesWOC[0] as Number ) + 1;
node.move( ( nodeWidth + _hSpace )* ( (nodesWOC[0] as Number) - 1) + _hSpace, (nodeHeight + _vSpace) * (level - 1) + _vSpace );
return [ (nodeWidth + _hSpace )* ( (nodesWOC[0] as Number) - 1) + _hSpace, (nodeHeight + _vSpace) * (level - 1) + _vSpace];
}
else{
// Placing a parent node
var positionX:Number;
var positionY:Number;
var posFirst:Array;
var posLast:Array;
var posTemp:Array;
// Placing all children first
for( var iNode:Number = 0; iNode < node.childNodes.length; iNode++ ){
var childNode:Node = node.childNodes[iNode];
posTemp = placeNode(childNode, level + 1, nodesWOC);
if( iNode == 0){
posFirst = posTemp;
}
if( iNode == node.childNodes.length - 1){
posLast = posTemp;
}
}
// Calculating position for parent node based on first and last children
positionX = ( (posFirst[0] as Number) + (posLast[0] as Number) )/2;
positionY = (nodeHeight + _vSpace) * (level - 1) + _vSpace;
node.move(positionX, positionY);
return [positionX, positionY];
}
}
Well, that's all the "difficult" part, I'm sure you can handle what is not explained here.
I hope you liked it.