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.

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

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 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

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.


Post a Comment