In our first
article, we went over the basics of data visualization, created a
data set, and generated a display showing occurrences of keywords in the
SitePoint forums. In the
second
part, we replaced the output with a spring graph—the better to
illustrate relationships between keywords—and also added some font scaling
to show the relative popularity of each keyword. In our final installment,
we’ll add a few more pieces of information to the display and refactor our
application in accordance with best practices. The instructions will build
upon the finished code from the second tutorial, but you might want to
download the Flex
Builder project archive containing all the finished code.
Again this week we have an Article
Quiz sponsored by Adobe to test what you learn in the tutorial. Be
sure to check
it out when you’re done here!
At the end of the second article we were left with a spring graph
that looked like this:
This allows us to see direct relationships between keywords, and
also see their relative popularity. It does leave a few things to be
desired though. To start with, we’ve no way of seeing the actual
occurrence counts of each keyword, and since we’re scaling the font size
in fixed increments, there can be a considerable difference in counts
between keywords that display at the same size.
Adding more text to the graph will probably make it a little messy,
and the exact occurrence count is more of a secondary piece of data that
people will drill down to if they’re interested in gleaning more
information. With this in mind, a good way to proceed is to add a
tooltip—this way the user can mouse over any keyword they’re interested in
and see the info they want, without it clogging up the graph
unnecessarily.
The easy way to do this is to add the count to the toolTip attribute on our item renderer. Let’s
try doing this now:
Example 1. (excerpt)
<sg:SpringGraph
id="mySpringGraph"
backgroundColor="#FFFFFF"
lineColor="#6666ff"
repulsionFactor="5"
right="10" left="10" bottom="10" top="10">
<sg:itemRenderer>
<mx:Component>
<mx:VBox
cornerRadius="5"
backgroundColor="0x444444"
paddingBottom="5"
paddingLeft="5"
paddingRight="5"
paddingTop="5"
toolTip="{data.data}">
<mx:Label
text="{data.id}"
fontSize="10"
color="0xFFFFFF"
textAlign="center" />
</mx:VBox>
</mx:Component>
</sg:itemRenderer>
</sg:SpringGraph>If you look at the toolTip
attribute that we added to the VBox in
our inline item renderer component, we’re passing it the
data key in our object. This might appear confusing:
just remember that what’s being passed to the item renderer is always
given the reference data in Flex, and it just so
happens that our Item class has a property called
data, which we’re using to store the keyword
occurrence count. If our property in the Item class
was called occurrence_count, the code would look like
this:
toolTip="{data.occurrence_count}"Save the above code—and when you mouse over a keyword in the
resulting spring graph, you should see a tooltip like this:
This works, and proves that we can push our information through to
the tooltip, but is neither attractive nor particularly helpful. Ideally
we want prettier interface, and one big enough to contain some extra text
like “Mentioned in 12345 Threads.”
Before we begin though, a note on best practice. In part
II, we simply created our item renderer inline—it was easier and
allowed us to explore the concept without a lot of code architecture
getting in the way. However, in the real world, it’s much better to create
an independent class for the item renderer. Doing so provides us with
encapsulation and allows us to reuse the same tooltip in multiple places,
should we wish to. In the context of this spring graph application, it may
make little sense to you to separate these elements, especially if you
lack experience working with larger projects. In the long run, however, I
guarantee you’ll find life much easier if you follow these principles.
Remember, when it’s a tiny demo application it seems like a lot of work,
but once your codebase reaches several hundred thousand lines of code, it
will be much simpler to debug, refactor, or modify any part of your
application if you’ve followed this sort of best practice.
The whys and wherefores of architectural best practice are beyond
the scope of this article, so for now I’ll just lay out a simple way to
structure the code. If you’re new to this, I highly recommend you do some
reading on Flex architecture and design patterns, and perhaps look into
some of the frameworks like Mate and Swiz.
In order to separate out our item renderer, we need to create a new
class. In your src directory you should have a
subdirectory named com. Inside
com, create a directory named
sitepoint, then inside that create another directory
named itemrenderers. This method of laying out
folders is sometimes called reverse domain
notation, and is just a handy way of separating code from
different projects. As long as the paths are correct, Flex is unconcerned
with where our code lives—we’re creating subdirectories purely to make it
easier to manage as the codebase grows or as you combine code from
different projects into one. In our case, you can see we already have a
directory—adobe—inside our com
directory; this is where the Adobe JSON serialization classes live.
Already you can see how sticking to this domain notation automatically
protects us from any naming clashes and keeps it all nice and
clean.
Now inside the Flex Navigator pane, right-click the
itemrenderers folder and choose
, then . Call it KeywordRenderer.mxml
and base it off VBox. You should end up with a new file whose contents
look like this:
Example 2. src/com/sitepoint/itemrenderers/KeywordRenderer.mxml
(excerpt)
<?xml version="1.0" encoding="utf-8"?> <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300"> </mx:VBox>
The code we need to complete the renderer is similar to the inline
version we built in the last tutorial—the complete file should look like
this:
Example 3. src/com/sitepoint/itemrenderers/KeywordRenderer.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox
xmlns:mx="http://www.adobe.com/2006/mxml"
cornerRadius="5"
borderThickness="1"
borderStyle="solid"
backgroundColor="0x444444"
paddingBottom="5"
paddingLeft="5"
paddingRight="5"
paddingTop="5">
<mx:Label
text="{data.id}"
fontSize="{parentDocument.getFontSize(data.data)}"
color="0xFFFFFF"
textAlign="center" />
</mx:VBox>Now that this is complete, we go back to our main
.mxml file and make a couple of changes. Remove the
inline component—everything inside and including the <sg:itemRenderer> tags. It should end up looking
like this:
Example 4. src/SitePoint_DataViz_Tutorial_Part3.mxml
(excerpt)
<sg:SpringGraph id="mySpringGraph" backgroundColor="#FFFFFF" lineColor="#6666ff" repulsionFactor="5" right="10" left="10" bottom="10" top="10" />
Note that I’ve removed the end tag and added a trailing slash to the
start tag, because we no longer need to nest anything inside it. We’ll
also need to move the font stepping code (which we wrote in Part II) of
the series to inside the item renderer class. This will prevent us from
having to use the parentDocument call to the font
function. It’s always a good idea to keep your components from being
dependent on the rest of your code, so they can be reused without
modification.
As our font method relies on data calculated in the main
application, we’ll need to pass it in as a series of parameters. We can
just copy and paste the getFontSize method into
our new item renderer, and then add declarations for the public variables
like so:
Example 5. src/com/sitepoint/itemrenderers/KeywordRenderer.mxml
(excerpt)
public var dataMin:Number; public var dataMax:Number; public var fontMin:Number; public var fontMax:Number; public var fontLift:Number;
To allow us to set these parameters, we’ll need to assign the
itemRenderer in our main application file. Let’s add
that to the setup method, just before the line
where we assign the dataProvider-:
Example 6. src/SitePoint_DataViz_Tutorial_Part3.mxml
(excerpt)
var factory:ClassFactory = new ClassFactory(KeywordRenderer)
factory.properties = {fontMin:fontMin, fontMax:fontMax, fontLift:fontLift, dataMin:dataMin, dataMax:dataMax};
mySpringGraph.itemRenderer = factory;
mySpringGraph.dataProvider = myGraph;Here we’re creating a ClassFactory and
setting the parameters we need in an array
(factory.properties), then assigning the factory as the
item renderer for our spring graph. We need to do this in our script,
since we’re unable to set parameters if we simply assign it as an
attribute in our MXML tag.
Your final KeywordRenderer.mxml should look
like this:
Example 7. src/com/sitepoint/itemrenderers/KeywordRenderer.mxml
(excerpt)
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox
xmlns:mx="http://www.adobe.com/2006/mxml"
cornerRadius="5"
borderThickness="1"
borderStyle="solid"
backgroundColor="0x444444"
paddingBottom="5"
paddingLeft="5"
paddingRight="5"
paddingTop="5"
buttonMode="true"
mouseChildren="false"
useHandCursor="true"
click="this.searchForums()"
toolTip=" "
toolTipCreate="createCustomToolTip('Mentioned in '+formatter.format(data.data)+' threads', event)">
<mx:Script>
<![CDATA[
import mx.events.ToolTipEvent;
import com.sitepoint.tooltips.KeywordToolTip;
private var sitePointSearchURL:String = "http://www.sitepoint.com/forums/search.php?q=";
public var dataMin:Number;
public var dataMax:Number;
public var fontMin:Number;
public var fontMax:Number;
public var fontLift:Number;
private function createCustomToolTip(title:String, event:ToolTipEvent) : void {
var ktt:KeywordToolTip = new KeywordToolTip();
ktt.message = title;
event.toolTip = ktt;
}
private function getFontSize(data:Number) : Number {
var fontSize:Number = (data - dataMin) / fontLift;
if (fontSize < fontMin) {
return fontMin;
} else if (fontSize > fontMax) {
return fontMax;
} else {
return fontSize;
}
}
private function searchForums() : void {
navigateToURL(new URLRequest(sitePointSearchURL+data.id), "_self");
}
]]>
</mx:Script>
<mx:NumberFormatter id="formatter" />
<mx:Label
text="{data.id}"
fontSize="{getFontSize(data.data)}"
color="0xFFFFFF"
textAlign="center" />
</mx:VBox>If you compile and reload your application, nothing will have
changed … perfect! That’s exactly what you want! When refactoring, the
goal is to keep the behavior of the application the same, while providing
the warm fuzzy feeling of knowing you’ve architected your code properly
for the future.
When creating custom item renderers (especially for components
like data grids) you need to take performance into account. For our
simple purposes, the above item renderer is perfectly sufficient. If,
however, you’re creating a renderer for a heavy data grid, for example,
you’d be better off creating a pure ActionScript item renderer. This has
no reliance on a container, as elements like Canvas and VBox can take a toll on performance when
dozens or hundreds are created in rendering a grid.






