Build an OOP Tag Cloud in ActionScript 3.0

Sep 28th in ActionScript, XML by Bruno Crociquia

In this tutorial I'll show you how to build a flexible, animatable tagcloud using an Object Oriented Programming approach. I don't believe in a right or wrong method, but rather several degrees of efficiency. If you have any constructive criticism on my code feel free to comment.

That said, lets start!

PG

Author: Bruno Crociquia

Hello, I'm a Senior Flash Developer at WIZ Interactive -> shamless plug? Designer by trade, developer by heart, I always try to bring something new to all projects I start. That said, as someone once told me, "don't aim to perfect, aim to excellence". I'm still trying...

Step 1: How to Think Cloud

This step is the most important one as it will dictate all the following steps. I start by looking at what I want to achieve and then break it into pieces, here's my line of thought:

I want to be able to add multiple tag clouds in a page. I want it to be simple and customizable. So what do I need to build this tag cloud?

I need a word array, a color, a font, minimum and maximum size definitions, oh and I need tag cloud elements to store that information, these elements should be textField based. Since I want several clouds the obvious choice is to create an instanceable tagCloud class that in this case will extend a Sprite.

Here's what my main function should look like:

var tagCloud:TagCloud = new TagCloud(words,font,color,minFontsize,maxFontsize,fullsize)

As you can tell there are plenty of parameters which need to be defined, the following will walk you trough the process. Create the following files:

  • MainTagCloud.fla - this file will instantiate the tagcloud
  • TagCloud.as - this is the class that will create the tagcloud
  • TagCloudElement.as - this is the element that will populate the tagcloud

Step 2: Building the Mother Class

Open TagCloud.as and write this code

package 
{

	public class TagCloud extends Sprite 
	{
		
		public function TagCloud($word_array:Array,$font="Arial",$minFontSize:Number=10,$maxFontSize:Number=30,$elementColor:Number=0xffffff,$fullsize:Number=200):void 
		{
			//here I assign the variables I receive to the class's variables
			wordArray = $word_array;
			font = $font
			minFontSize = $minFontSize
			maxFontSize = $maxFontSize
			elementColor = $elementColor
            fullsize = $fullsize
			//after setting the variables I build the cloud
			buildTagCloud();
		}
}

import these libraries:

	import flash.text.Font;
	import TagCloudElement; // I'll get to this one later on
	import flash.display.Sprite;
	import flash.events.Event;

define these variables:

		public var cloudElements:Array;
		private var wordArray:Array;
		private var word:String;
		private var relevancy:Number;
		private var size:int;
		private var element:TagCloudElement;
		private var minFontSize:Number;
		private var maxFontSize:Number;
		private var elementColor:Number;
		private var font:String;
		private var wordLength:int
        private var fullsize:Number

You'll end up with something like this:

package 
{
	//First import these packages:
	import flash.text.Font;
	import TagCloudElement; // I'll get to this one later on
	import flash.display.Sprite;
	import flash.events.Event;

	//Create a class that will extend a sprite
	public class TagCloud extends Sprite 
	{
		//we need these variables to be abble to create the tagCloud
		public var cloudElements:Array;
		private var wordArray:Array;
		private var word:String;
		private var relevancy:Number;
		private var size:int;
		private var element:TagCloudElement;
		private var minFontSize:Number;
		private var maxFontSize:Number;
		private var elementColor:Number;
		private var font:String;
		private var wordLength:int
        private var fullsize:Number
		
		public function TagCloud($word_array:Array,$font="Arial",$minFontSize:Number=10,$maxFontSize:Number=30,$elementColor:Number=0xffffff,$fullsize:Number=200):void 
		{
			//here I assign the variables I receive to the class's variables
			wordArray = $word_array;
			font = $font
			minFontSize = $minFontSize
			maxFontSize = $maxFontSize
			elementColor = $elementColor
            fullsize = $fullsize
			//after setting the variables i build the cloud
			buildTagCloud();
		}
	}
}

Step 3: Construct Your Main Function

Here's the main function that will build our cloud.

private function buildTagCloud() {
			//create an element array
			cloudElements = new Array();
			//gets the words lenght so i can iterate trought them and create the elements
			wordLength = getSingleWordList(wordArray).length
			for (var i=0; i<wordLength; i++) {
				//this function returns me an array, its basically a filter, read more about it later on
				word = getSingleWordList(wordArray)[i]
				//this function uses the wikipedia formula to calculate the element size
				size = setElementSize(word, wordArray, minFontSize, maxFontSize);
				//creates a new element
				element = new TagCloudElement(word, size, font, elementColor);
				//stores the new element in the array
				cloudElements[i] = element
				//sets the transparency based on the size
				cloudElements[i].alpha=size/maxFontSize
				//just a random way to display the cloud elements
				cloudElements[i].x = Math.random() * fullsize
				cloudElements[i].y = Math.random() * fullsize
				addChild(cloudElements[i]);
				//performs a hit test trought the created objects
				cloudHitTest(i)
			}
		}

Step 4: Adding a Word Counter

Let's see how many words we're dealing with.

private function countWord($word:String,$array:Array):int {
		var count:int=0;
			for (var i:int=0; i<$array.length; i++) {
				if ($array[i].toLowerCase()==$word.toLowerCase()) {
					count+=1;
				}
			}
			return (count);
		}

Step 5: Set the Element Size

I set the element size by using a formula found on wikipedia:

function setElementSize($word:String, $array:Array, $minSize:Number, $maxSize:Number):Number {
			var $size:Number = $maxSize * countWord($word, $array) / $array.length
			$size *= $minSize
			return $size
		}

Step 6: Creating a Single Word List

This calls a filter for the array.

private function getSingleWordList($source:Array):Array {
			var $array:Array=$source.filter(singleWordFilter);
			return $array;
		}

Now set the filter rules.

private function singleWordFilter(element:*, index:int, arr:Array):Boolean {
			if(arr[index+1]){
				if (arr[index].toLowerCase()!=arr[index+1].toLowerCase()) {
					return true;
				} else {
					return false;
				}
			}else {
				return false;
			}
		}

Step 7: How to HitTest

We're going to need to test for overlapping positions.

		private function cloudHitTest($i) {
			for (var a:int=0; a < $i; a++) {
				//if HITS
				if (cloudElements[a].hitTestObject(cloudElements[$i])) {
					//Reposition
					cloudElements[$i].x = Math.random() * fullsize
					cloudElements[$i].y = Math.random() * fullsize
					addChild(cloudElements[$i]);
					//and test again
					cloudHitTest($i)
				}
			}
			
		}

Step 8: Setting up an Element Getter

This is just a getter of an element by name, in case I need one over the main timeline.

		public function getElementByName($name:String):TagCloudElement {
			var $auxCloudElement:TagCloudElement;
			for (var i:int=0; i < wordLength; i++) {
				if (cloudElements[i].word == $name) {
					$auxCloudElement =  cloudElements[i]
				}
			}
			return $auxCloudElement
		}

Step 9: Inside the Element Class

package 
{
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.Font;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.text.TextFieldAutoSize;
	import flash.text.AntiAliasType;
   	import flash.text.GridFitType;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;


	public class TagCloudElement extends Sprite 
	{
		public var word:String;
		public var urlpath:String;
		
		private var textCloudFormat:TextFormat;
		private var textCloud:TextField;
		
		public var font:String;
		public var size:Number;
		public var color:Number;
		
		// Same constructor as TagCloud, the element extends a Sprite
		// and Builds the Element based on a TextField
		public function TagCloudElement($word:String, $size:Number = 10, $font:String = "Arial", $elementColor:Number = 0xffffff):void 
		{
			word = $word
			font = $font
			size = $size
			color = $elementColor
			
			buildElement();
		}
		
		private function buildElement() {
			//creates the textformat
			textCloudFormat = new TextFormat();
			// defines the font size and color
			textCloudFormat.font = font
			textCloudFormat.size = size
			textCloudFormat.color = color
			// creates a textField
			textCloud = new TextField();
			// embeds the font
			textCloud.embedFonts=true;
			//sets the antialias to readable equivalent
            textCloud.antiAliasType=AntiAliasType.ADVANCED;
			//defines its text
            textCloud.text=word
			//defines its size as automatic
            textCloud.autoSize=TextFieldAutoSize.LEFT;
			//fit to pixel
			textCloud.gridFitType = GridFitType.PIXEL
			// unselectable text
			textCloud.selectable = false;
			// assigns the textformat to the textfield
			textCloud.setTextFormat(textCloudFormat)
			// adds the MouseEvents listeners
			textCloud.addEventListener(MouseEvent.ROLL_OVER,rollOverCloudElement)
			textCloud.addEventListener(MouseEvent.ROLL_OUT,rollOutCloudElement)
			textCloud.addEventListener(MouseEvent.CLICK,clickCloudElement)
			addChild(textCloud);
		}
		
		private function rollOverCloudElement(e:MouseEvent){
			e.target.textColor = 0x666666;
		}
		private function rollOutCloudElement(e:MouseEvent){
			e.target.textColor = color
		}
		// I've made a link to a twitter search using the word selected.
		private function clickCloudElement(e:MouseEvent){
			navigateToURL(new URLRequest("http://search.twitter.com/search?q="+e.target.text),"_blank");
		}
	}
}

Step 10: Implementation

Now, all that's left to be done is to implement this class in a real .fla file with all the stuff that you are accustumed to (ie:timeline) :P

You'll need to create a font so you can display the textFields, I embeded an Arial font.

Then in the first frame of your .fla import the TagCloud class, set a stage.align to the top left (so that we can find the stage middle position without much work) and create a new instance of the font we just added to the library:

import TagCloud;

stage.align = StageAlign.TOP_LEFT
var wordArray:Array;
var tagCloud:TagCloud;
var arial:Arial = new Arial();//sets a new instance of Arial (already in the library)

function init() {
	//creates an array to populate the cloud
	wordArray = new Array("In","this","fashion,","text","clouds","may","become","a","generally","applied","tool","for","managing","growing","information","overload","by","using","automated","synthesis","and","summarization","In","the","information","saturated","future","or","the","information","saturated","present");
	//sorts the array alphabetically so i can filter later on
	wordArray.sort();
	// creates a new tagCloud instance
	tagCloud = new TagCloud(wordArray,arial.fontName,15,20,0x000000);
	// center's it to stage
	tagCloud.x = stage.stageWidth*0.5-tagCloud.width*0.5
	tagCloud.y = stage.stageHeight*0.5-tagCloud.height*0.5
	//adds to stage
	addChild(tagCloud);
}

init();

Step 11: Build an RSS Feed Request

Now we need to grab a feed from somewhere so we can cloud it. I chose the CNN news feed. To be able to load an XML you need 4 objects including a urlRequest that will be used as a path to the feed.

var requestFeed:URLRequest = new URLRequest("http://rss.cnn.com/rss/cnn_world.rss");
//an urlLoader so that we can load the request we need to make
var loaderFeed:URLLoader = new URLLoader()
// a XML object so we can store the data we recieve from the feed
var xmlFeed:XML;
//and last but not least a title array that i can explode the words from...
var titleWords:Array;

Step 12: The Initialization Method

Now inside our main function I need to add the complete event handler to the request so that it can be called upon a successful load.

function init() {
	loaderFeed.addEventListener(Event.COMPLETE,onFeedComplete)
	//I will need the wordArray to be instantiated so I can store the words inside the feed
	wordArray = new Array()
	//we are ready to load the XML now
	loaderFeed.load(requestFeed);
	
}

Step 13: The Data Structure

The data structure is stored inside the e.target.data so we create the XML here by doing:

function onFeedComplete(e:Event){
	xmlFeed = new XML(e.target.data)
	//after viewing the source of the rss feed I noticed the structure was something like channel.item.title so i'm using the titles as my word source.
	//I need to make an array to store all the words of a title and then add each on of those words inside the word array
	//for this I cycle through them

	for(var i:uint=0;i<xmlFeed.channel.item.length();i++){

Step 14: Building the Word List

Instanciate the titleWords in every iteration so that you have a clean array everytime we have a new title.

		
titleWords = new Array()
		//to make single words I split them on "space"
		titleWords = xmlFeed.channel.item[i].title.split(" ")
		//after them being split i iterate them to be added to the wordArray
		for(var j:uint=0;j<titleWords.length;j++){
			//i use lowercase so i don't have any duplicated words
			wordArray.push(titleWords[j].toLowerCase());
		}
		
	}
    //after that being done I sort the word array alphabetically
	wordArray.sort();
	//and I start the tagCloud
	startTagCloud();    

Step 15: Starting the Tag Cloud

Now we have all the elements we need to make this tag cloud.

try{
		tagCloud = new TagCloud(wordArray,arial.fontName,20,40,0xFFFFCD,300);
	}catch(e:Error){
		startTagCloud()
	}
	//all that is left is to define an X and a Y
	tagCloud.x = stage.stageWidth*0.5-tagCloud.width*0.5
	tagCloud.y = stage.stageHeight*0.5-tagCloud.height*0.5
	//and adding it to the stage
	addChild(tagCloud);
	//tadaaa we are done.. 
}

//don't forget to initialize the main function :)
init();

Step 16: The Final Code

Here's the complete code for you to read fully.

import TagCloud;
stage.align = StageAlign.TOP_LEFT

var wordArray:Array;
var tagCloud:TagCloud;
var arial:Arial = new Arial();

var requestFeed:URLRequest = new URLRequest("http://rss.cnn.com/rss/cnn_world.rss");
var loaderFeed:URLLoader = new URLLoader()
var xmlFeed:XML;
var titleWords:Array;

function init() {
	loaderFeed.addEventListener(Event.COMPLETE,onFeedComplete)
	wordArray = new Array()
	loaderFeed.load(requestFeed);
}

function onFeedComplete(e:Event){

  xmlFeed = new XML(e.target.data)
  
  	for(var i:uint=0;i<xmlFeed.channel.item.length();i++){
  		titleWords = new Array()
  		titleWords = xmlFeed.channel.item[i].title.split(" ")
  		for(var j:uint=0;j<titleWords.length;j++){
  			wordArray.push(titleWords[j].toLowerCase());
		}
	}
  wordArray.sort();
  startTagCloud();
}

function startTagCloud(){
  try{
 	  tagCloud = new TagCloud(wordArray,arial.fontName,20,40,0xFFFFCD,300);
   }catch(e:Error){
  	  startTagCloud()
	}
  tagCloud.x = stage.stageWidth*0.5-tagCloud.width*0.5
  tagCloud.y = stage.stageHeight*0.5-tagCloud.height*0.5
	
  addChild(tagCloud);
}

init();

Conclusion

I could have used linked lists and while loops to make this a bit faster, but you'll find it reasonably quick. One final note: be sure to set the random size big enough or you'll get a stackOverFlow error when the cloudElement can't find a place to be put.

I hope you liked this tutorial, thanks for reading!


Enjoy this Post?

We'd love your vote!

User Comments

( ADD YOURS )
  1. PG

    David Nash September 28th

    Nice

    ( Reply )
  2. PG

    Dario Gutierrez September 28th

    Bruno definitely this is an excellent tut. Nice code.

    ( Reply )
  3. PG

    Shurandy Thode September 28th

    Great tut.

    ( Reply )
  4. PG

    Roberto XSM September 28th

    Very good. Thanks

    ( Reply )
  5. PG

    Web 2.0 September 29th

    Great work :)

    ( Reply )
  6. PG

    André September 29th

    very nice, very well coded man, thanks a lot for this tutorial

    ( Reply )
  7. PG

    Jirka September 29th

    hi, i have problem with open .fla file. I have CS3. Can you save it for my version and send it? Thank you very much

    ( Reply )
    1. PG

      Bruno Crociquia September 29th

      Here you go,

      it will only exist for the next 24h or 20downloads…

      http://tiny.cc/upJyp

      ( Reply )
    2. PG

      Ian Yates October 1st

      Thanks Bruno,

      I grabbed a copy and included it in the existing source.zip. CS3 is therefore now available from the source link at the top of the page.

      ( Reply )
      1. PG

        Bruno Crociquia October 2nd

        Thanks Ian

  8. PG

    wang ruyi October 5th

    nice tutorial!

    ( Reply )
  9. PG

    Pixel Maestro October 6th

    There is a Wordpress TagCloud worth looking at http://www.roytanck.com/tag/wp-cumulus/

    Flash source files are in the Development version
    http://wordpress.org/extend/plugins/wp-cumulus/download/

    ( Reply )
  10. PG

    Flash Framer October 7th

    OOP always fries my brain but you did a great job. Thanks!

    ( Reply )
  11. PG

    Maria Luiza November 3rd

    Maravilhoso, muito bom

    ( Reply )
  12. PG

    user December 16th

    hey,

    first of all i appreciate your tutorial.
    but i have the problem that the source files only work for
    your tag cloud example with predefined “words” in your tagCloud.

    if i edit the main with the new code, or generate it completly new
    with your tutoial i get an error “only expected 5 arguments” referring
    to your constructor for your tagCloud, but which you want to initalize
    witz 6 arguments:
    [...]
    tagCloud = new TagCloud(wordArray,arial.fontName,20,40,0xFFFFCD,300);
    [...]

    even if i fix this (reducing it to the 5 arguments it won’t load or work
    with the xml file from cnn.rss or any other.

    i would appreciate your help, or instead see the complete source file with
    the coude from this tutorial and not with the example without the url loader.

    thanks in advance.
    greetz.

    ( Reply )
    1. PG

      Bruno Crociquia January 26th

      Sorry, for only reading your question now. Use mainTagCloud_cs3.fla version inside, as it has the xml loader instead of the other fla that has a static name array.

      remember, you can’t test the swf in your local machine, because we are accessing http we can only work within a network sandbox. meaning if you want to run inside your computer you need to either host a server in your machine, or edit your flash player security settings.

      Cheers

      ( Reply )
  13. PG

    kedicik February 6th

    i found bag! Because i don’see last word in array

    ( Reply )
  14. PG

    Aaron February 7th

    awesome tutorial! I like that you go from scratch in your code, really helps to walk through the process. Thanks!

    ( Reply )
  1. Arrow
    Gravatar

    Your Name
    February 7th