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!
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!
User Comments
( ADD YOURS )David Nash September 28th
Nice
( )Dario Gutierrez September 28th
Bruno definitely this is an excellent tut. Nice code.
( )Shurandy Thode September 28th
Great tut.
( )Roberto XSM September 28th
Very good. Thanks
( )Web 2.0 September 29th
Great work
( )André September 29th
very nice, very well coded man, thanks a lot for this tutorial
( )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
( )Bruno Crociquia September 29th
Here you go,
it will only exist for the next 24h or 20downloads…
http://tiny.cc/upJyp
( )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.
( )Bruno Crociquia October 2nd
Thanks Ian
wang ruyi October 5th
nice tutorial!
( )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/
Flash Framer October 7th
OOP always fries my brain but you did a great job. Thanks!
( )Maria Luiza November 3rd
Maravilhoso, muito bom
( )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.
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
( )kedicik February 6th
i found bag! Because i don’see last word in array
( )Aaron February 7th
awesome tutorial! I like that you go from scratch in your code, really helps to walk through the process. Thanks!
( )