Web sites such as del.icio.us, Technorati and Flickr, which allow tagging of their content, depict the popularity of tags using a tag cloud. The popular items have larger font sizes. This concept can be applied to any list of items where each item has a weight. For example, a list of products can be displayed in a cloud weighed by the cost of a product. Using the ASP.NET server control presented in this article you display your own domain specific items as a cloud. The image below shows a sample cloud for articles on Ajaxian. (I wanted to use CP for the sample image but I was not able to find a way to get item count per section).
This control works only with the ASP.NET 2.0. First, you need to add a reference to the control assembly or the project to your web site. In the page you need to use the control add the following declaration.
<%@ Register Namespace="VRK.Controls" TagPrefix="vrk" Assembly="VRK.Controls" %>
This will allow you to use the control in the page using the following declaration.
<vrk:Cloud ID="c1" runat="server" />
Now you need to add items to the control. Each item will be displayed as an hyperlink on the page. You can supply following properties for the items you want to add.
Text - The text of the hyperlinkHref - The URL where the user will be navigated to when he
clicks the hyperlink. If this property is left blank the control causes a postback
and raises the ItemClick event.Title - The tooltip text of the HTML anchor.Weight - The weight determines how the item will be displayed.
The control converts the range to weights of data to a normalized weight range
that lies between 1 and 7. The normalized weight determines how the item is
displayed. You can add the items declaratively as shown:
<vrk:Cloud ID="c1" runat="server">
<Items>
<vrk:CloudItem Text="Item1"
Href="Default.aspx?tag=Item1"
Title="Some title" Weight="4" />
<vrk:CloudItem Text="Item2"
Href="Default.aspx?tag=Item2"
Title="Some title" Weight="4" />
</Items>
</vrk:Cloud>
You can also add the Item programmatically:
c1.Items.Add(new CloudItem("Item1", 4,
"Default.aspx?tag=Item1",
"Some title"
));
You can also use data binding to add items. First, you need to a data source
to your page. The code example below shows an ObjectDataSource but
you can use any ASP.NET DataSourceControl such as SqlDataSource
or AccessDataSource.
<asp:ObjectDataSource ID="ItemsSource" runat="server" SelectMethod="GetItems" TypeName="CloudTest.ItemsSource" />
You need to indicate to the cloud control that it needs to use the ItemsSource
data source control by specifying its DataSourceID.
<vrk:Cloud ID="c1" runat="server" DataSourceID="ItemsSource" .... />
Once the data source is specified, you will need to indicate how the items should be populated from the data source. Following control properties can be used to supply the information:
DataTextField - the name of the data field that is bound to
the Text property of an item. DataTextFormatString - the format string for the Text property.
{0} in the string is replaced with the value of the field from the data source.DataHrefField - the data field which is bound to the Href property
of an item.DataHrefFormatString - the format string to format the Href
property value.DataTitleField - the data field which is bound to the Title
property of an item.DataTitleFormatString - the format string for the title (tooltip)
of an item.DataWeightField - the field from the Data Source where the
weight of an item is to be obtained.The control than normalizes the weight of all the items so that they fit in the
range 1 to 7. You can control the display of the normalized items using the optional
ItemCssClassPrefix property. If you use the ItemCssClassPrefix
property you need to add seven different CSS classes to your HTML page. For example,
if you specify the property value to be "Item", you need to specify CSS classes
Item1, Item2... Item7. If you don't specify
the ItemCssClassPrefix the font size CSS attribute is set depending
on the weight as following:
| Normalized Weight | font-size |
|---|---|
| 1 | xx-small |
| 2 | x-small |
| 3 | small |
| 4 | medium |
| 5 | large |
| 6 | x-large |
| 7 | xx-large |
Now let's examine how the control works.
The main logic is to convert the distribution of weights into a integral range that between 1 to 7. After struggling with statistics for some time I figured out the following algorithm for normalizing the weights.
Statistics class similar to Math
class.
private IEnumerable ItemWeights
{
get
{
foreach (CloudItem item in this.Items)
{
yield return item.Weight;
}
}
}
...
double mean;
double stdDev = Statistics.StdDev(ItemWeights, out mean);
The StdDev function takes an IEnumerable<double> parameter
which is supplied by the ItemWeights method.factor = (weight - mean)/(stddev)
| Normalized Weight | Condition |
|---|---|
| 1 | factor <= -2*stddev |
| 2 | -2*stddev < factor <= -1*stddev |
| 3 | -1*stddev < factor <= -0.5*stddev |
| 4 | -0.5*stddev < factor < 0.5*stddev |
| 5 | 0.5*stddev <= factor < 1*stddev |
| 6 | 1*stddev < = factor < 2*stddev |
| 7 | factor >= 2 * stddev |
Once the normalized weights are obtained it is easy to set the font sizes and the classes.
foreach (CloudItem item in Items)
{
HtmlAnchor a = new HtmlAnchor();
a.HRef = String.IsNullOrEmpty(item.Href) ?
this.Page.ClientScript.GetPostBackClientHyperlink(this, index.ToString()) :
item.Href;
a.InnerText = item.Text;
a.Title = item.Title;
int normalWeight = NormalizeWeight(item.Weight, mean, stdDev);
if (hasCssClassPrefix)
{
a.Attributes["class"] = this.ItemCssClassPrefix + normalWeight.ToString();
}
else
{
a.Style.Add(HtmlTextWriterStyle.FontSize, _fontSizes[normalWeight - 1]);
}
this.Controls.Add(a);
this.Controls.Add(new LiteralControl(" "));
index++;
}
The method seems to work reasonably well in most of the situations. Any suggestions to improve it further are welcome.