Introduction
A tag cloud is a way to display a weighted list such that the
weight of each item is reflected by the size of the item's text. Tag clouds provide a quick way for one to eyeball a list and
ascertain what items are more prevalent. Oftentimes, each item in a tag cloud is rendered as a link that, when clicked, allows
the user to drill into the selected category. For example, given any parent/child relationship in a database, we may be interested
in seeing how many children each parent has, relative to one another. The following image of a tag cloud lists the
categories in the Northwind database, with the size of each item is proportional to the number of products for each category,
relative to the other categories. (Each category name, if clicked, takes the user to a page that lists the products for that
particular category.)
Tag clouds are commonly used in websites that support tagging.
Tagging is a technique of assigning a set of string values to a particular item - such as classifying a picture of your pet dog
at a photo sharing website with string tags like "dog", "pet", "adorable", and so on. Tags provide an informal way for
users to easily classify content and make it easy to visualize the most common tags and to drill down into a specific tag.
However, tag clouds are not limited to websites that support tagging and can be used to offer an alternative view of a weighted
list (such as parent/child data in a database).
In this article we'll examine how to build a tag cloud in ASP.NET. In this article we'll focus on accomplishing this
through code in an ASP.NET page's code-behind class. In a future article, we'll move this code out of the ASP.NET page and
into a stand alone, custom, compiled server control that supports data binding, use of declarative data source controls,
and so on. Read on to learn more!
Deciding on the Tag Cloud's Font Sizing
When designing a tag cloud, it is important to first determine how the font sizes will vary by item. The font sizes can be
specified using relative font sizes - xx-small, medium, x-large, and so on - or by absolute font sizes - 12pt, 32pt, etc. The
benefit of using relative font sizes is that the fonts will scale based on a user's browser's text size settings. Another
design decision to make is the spectrum of font sizes. That is, how many distinct font sizes are allowed? For the code
we'll examine shortly, I've decided to use seven absolute font sizes: xx-small, x-small, small, medium, large, x-large,
xx-large.
Another vital design decision is how the font sizing is selected based on the distribution of the weights. For my code, I've
decided to use a simple linear distribution. If the weighted list ranges from a minimum value of, say, 60, to a maximum value of
200, then I break down each of the seven font sizes into ranges plotted linearly along the minima and maxima. In this example,
the delta is 140 (200 - 60). Divided by 7 results in each font size having a range of 20 units. So for any list items whose
weight is between 60 and 80, I use font size xx-small; for those between 80 and 100, I use x-small; and so on.
While this linear scale is simple to implement and works well for evenly distributed data points, if you have skewed data points
you'll end up with potentially extreme results. Consider the case where the weighted list being displayed has one data point with a
weight of 60 (our minimum) and five data points between weights 180 and 200. The result is that the first data point will be displayed
with the smallest font size (xx-small) and all remaining five will be displayed in the largest font size (xx-large). But this
doesn't show the variation between the five in the 180-200 range. To capture this, an alternative model is to base the font sizes
on the standard deviation of the distribution, which captures
how far each data point deviates from the mean (the average). For more on this, check out Cloud Control for ASP.NET.
It's an open source tag cloud control for ASP.NET created by Rama Krishna Vavilala that uses the standard deviation model.
Building the Cloud
Ideally, a tag cloud could be associated with some data, a few properties set, and, voila, the tag cloud appears! In fact,
we'll examine how to accomplish exactly this in a future article by creating a custom, compiled ASP.NET 2.0 server control.
For now, though, let's just implement the tag cloud directly from an ASP.NET page (although this could be moved to a User Control
for greater reuse opportunities).
First things first - we need the data that returns the list with each item weighted. In the demo downloadable at the end of
this article, I have used a SqlDataSource control to query the Northwind database, returning the CategoryID,
CategoryName, and number of products belonging to each category:
SELECT Categories.CategoryID, Categories.CategoryName,
COUNT(Products.ProductID) AS NumberOfProducts
FROM Categories
INNER JOIN Products ON
Categories.CategoryID = Products.CategoryID
GROUP BY Categories.CategoryID, Categories.CategoryName
ORDER BY Categories.CategoryName
This query uses the GROUP BY clause to return the count of products associated with each category.
See Using the GROUP BY Clause for more information
on this SQL clause.
The tag cloud is outputted in the Web page via a Literal Web control named CloudMarkup. In code we're going to loop
through the database results, compute the font size scale, and then emit an HTML hyperlink as markup into the
Text property of the Literal control. To start, we need to get the data from the SqlDataSource control. This is
accomplished by calling its Select() method, which returns a DataView object:
'First, read data from SqlDataSource
Dim cloudData As DataView = CType(CategoriesProductsBreakdownDataSource.Select(DataSourceSelectArguments.Empty), DataView)
Next, a series of constants are defined in an attempt to generalize this code at least a little bit. For example, there
are constants that define the names of the database columns that return the weight, the text field to display, along with
the field to use (and a format string) when constructing the URL for each hyperlink. You'll also find the set of font sizes
and the markup to inject between each link.
Const SpacerMarkup As String = " " 'The markup injected between each item in the cloud
Dim FontScale() As String = {"xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"}
'All database column names are centralized here. To customize this, simply modify the column names here
Const WeightColumnName As String = "NumberOfProducts"
Const TextColumnName As String = "CategoryName"
Const NavigateUrlColumnName As String = "CategoryID"
Const NavigateUrlFormatString As String = "~/ViewProductsByCategory.aspx?CategoryID={0}"
Next, we need to determine the minimum and maximum weight values in the list. This information is then used to compute
the linear scale by which we'll map an item's weight to a font size. The scaleUnitLength holds the "length" of
each notch on the scale.
Dim minWeight As Decimal = Decimal.MaxValue, maxWeight As Decimal = Decimal.MinValue
For Each row As DataRowView In cloudData
Dim numProductsObj As Object = row(WeightColumnName)
If Not Convert.IsDBNull(numProductsObj) Then
Dim numProductsDec As Decimal = Convert.ToDecimal(numProductsObj)
If numProductsDec < minWeight Then minWeight = numProductsDec
If numProductsDec > maxWeight Then maxWeight = numProductsDec
End If
Next
Dim scaleUnitLength As Decimal = (maxWeight - minWeight + 1) / Convert.ToDecimal(FontScale.Length)
After computing the scale, the data is enumerated one more time, this time with a hyperlink (<a>) element
emitted for each record. To find the place on the scale, the current item's weight is subtracted from the minimum and
divided by scaleUnitLength. This index is used to select the appropriate font size from FontScale.
Also note that the specified values for NavigateUrlColumnName and NavigateUrlFormatString are used
to configure the href portion of the hyperlink.
For Each row As DataRowView In cloudData
Dim numProductsObj As Object = row("NumberOfProducts")
If Not Convert.IsDBNull(numProductsObj) Then
Dim numProductsDec As Decimal = Convert.ToDecimal(numProductsObj)
Dim scaleValue As Integer = Math.Truncate((numProductsDec - minWeight) / scaleUnitLength)
CloudMarkup.Text &= String.Format("<a href=""{0}"" style=""font-size:{1};"">{2}</a>{3}", _
Page.ResolveUrl(String.Format(NavigateUrlFormatString, row(NavigateUrlColumnName).ToString())), _
FontScale(scaleValue), row(TextColumnName).ToString(), SpacerMarkup)
End If
Next
That's all there is to it! The resulting output is a chunk of HTML that, when rendered in the user's browser, lists each
category as a hyperlink pointing to ViewProductsByCategory.aspx?CategoryID=categoryID. Each link's text
size is based on its weight using a linear scale. The following screenshot below shows a tag cloud of the Northwind database's
categories table along with the raw data used to populate the cloud.
Conclusion
Tag clouds can provide an alternative means for displaying a weighted list. As we saw in this article, they can be implemented
with just a bit of code (although the standard deviation approach takes a bit more effort), and offer visual cues and a way
to quickly drill into the details of any item. In a future article we'll look at how to move this logic to a custom, compiled
ASP.NET 2.0 server control. Until then.....