Creating a Tag Cloud in C#

I wanted to add a TagCloud to GeekZilla and another bespoke development project. The first thing I did was look around the web for some examples (or free components). To my supprise none jumped out at me. So, I decided to write my own.

At first I found myself wondering how I would render different size words randomly within a <div>. How would I be able to position them in relation to each other? float:right?? After looking at a few tag coulds de.licio.us , dotnetkicks etc I discovered that tag clouds are infact displayed in alphabetical order and the word size doesn't change much.

My Needs

The TagCloud would need to be able to be generated from a DataTable containing the columns 'tag' and 'count'. In my case the data would come from a Sql Server 2005 database. At the database level I'd be doing the aggregation and ordering. The TagCould code would only need to worry about weighting the tags.

The Data

First of all I created a view of all tags with their grouping:

SELECT TOP (60) 
    COUNT(1) AS Count, 
    Tag
FROM         
    dbo.ContentTag
GROUP BY 
    Tag
ORDER BY 
    'Count' DESC

I then created a stored procedure which would return me the top 60 tags ordered alphabetically:

SELECT 
    * 
FROM 
    vwTagCloud 
ORDER BY 
    Tag

The UI

I decided to place the tag control within a UserControl incase I needed to include it in more than one page, in the end it was only used in the MasterPage.

I created a method called GetCloud() which would contain the logic for the tag weighting. The method accepts a DataTable and a String based Cloud Template. The Cloud Template is tokenised, the replaced version is returned for each tag in the DataTable.

/// <summary>
/// Return a tag could.
/// </summary>
/// <param name="tagData">DataTable containing the columns 'tag' and 'count'</param>
/// <param name="cloudTemplate">
///     The template to return for each iten. Should include $tag$ and 
///     $weight$ which will be replaced with actual data
///     <example>&lt;h$weight$&gt;&lt;a href='#$tag$'&gt;$tag$&lt;/a&gt;&lt;/h$weight$&gt;</example>
/// </param>
/// <returns>String containing tag cloud</returns>
private string GetCloud(DataTable tagData, string cloudTemplate)
{

Next I included some validation on the input table.

    // Check the input data
    if (!tagData.Columns.Contains("tag"))
        throw new Exception("Expected column 'tag' is missing");

    if (!tagData.Columns.Contains("count"))
        throw new Exception("Expected column 'count' is missing");

Now time to get down to business. To figure out the weight of the tag I first needed to know the Min and Max tag occurances. For this I used DataTable.Compute(). You can see more information on the Compute() method in my article about EvaluatingExpressions.

    // Lets get cracking
    StringBuilder outputBuffer = new StringBuilder();
    double max = 0;
    double min = 0;

    // Use Compute to get the min and max counts
    double.TryParse(tagData.Compute("min(count)"null).ToString(), out min);
    double.TryParse(tagData.Compute("max(count)"null).ToString(), out max);

With the Min and Max known I could loop through the DataTable figuring out the tag weight.

    // Loop through the data, generate the tag cloud
    foreach (DataRow row in tagData.Rows)
    {

First of all I needed to calculate the weight as a percentage. For this I divided the number of occuances by the maximum occurances and multiplied it by 100.

        // work out the weight
        double weightPercent = (double.Parse(row["count"].ToString()) / max) * 100;
        int weight = 0;

With the weight percentage I apply a bunch of cascading 'if' statements to figure out which weight classification the tag belongs to

        if (weightPercent >= 99)
        {
            //heaviest
            weight = 1;
        }
        else if (weightPercent >= 70)
        {
            weight = 2;
        }
        else if (weightPercent >= 40)
        {
            weight = 3;
        }
        else if (weightPercent >= 20)
        {
            weight = 4;
        }
        else if (weightPercent >= 3)
        {
            //weakest
            weight = 5;
        }
        else
        {
            // use this to filter out all low hitters
            weight = 0;
        }

Lastly I replace the tokens in the cloud template and append the result to a StringBuilder.

        // don't return data for unweighted items
        if (weight > 0)
            outputBuffer.Append(cloudTemplate.Replace("$weight$", 
                weight.ToString()).Replace("$tag$", 
                row["tag"].ToString()).Replace("$urlencodetag$",HttpUtility.UrlEncode(row["tag"].ToString())));
    }

When all are evaluated, return the StringBuilder

    return outputBuffer.ToString();
}

CSS

Ok, this code generates a bunch of Anchor tags. I'd need to use CSS to make the thing look decent. The cloud template used the weight to assign an appropriate style to each tag. The styles used are

  • weight1
  • weight2
  • weight3
  • weight4
  • weight5

The following StyleSheet was created to bring these tags to life within a <div> called tagCloud

<style>
#tagCloud
{
    width:238px;
    border:solid 1px #ccc;
    padding:5px;
    margin-bottom:10px;
    text-align:justify;
}

#tagCloud A
{
    text-decoration:none;
    margin-left:5px;
    margin-right:5px;
    font-family:Trebuchet MS, Verdana, Arial;
    text-transform:lowercase;
}

#tagCloud A:hover
{
    color:#00cc00;
    text-decoration:underline;
}

#tagCloud A.weight1
{
    color: #ff9900;
    font-size: 1.9em;
    font-weight:bolder;
}
#tagCloud A.weight2
{
    color: #4169e1;
    font-size:1.5em;
    font-weight:bolder;
}
#tagCloud A.weight3
{
    color: #009eff;
    font-size: 1.4em;
    font-weight:bolder;
}
#tagCloud A.weight4
{
    color: #4188cf;
    font-size: 1.2em;
}
#tagCloud A.weight5
{
    color: #83bcd8;
    font-size: 1.0em;
}
</style>

Adding my cloud to the UserControl

Html

The Html is really simple, as you can see below:

<div id="tagCloud">
      <asp:Literal ID="CloudLiteral" runat="server" />
</div>

Code Behind

The Page_Load() calls my DataAccessLayer to get the DataTable and passes the data to the local GetCloud() method. Note the use of ResolveClientUrl

protected void Page_Load(object sender, EventArgs e)
{
    String browseUrl = Page.ResolveClientUrl("~/TagBrowse.aspx");
    
    CloudLiteral.Text = GetCloud(ContentTag.GetCloudData().Tables[0], 
                "<a class='weight$weight$' href='" + browseUrl + "?tag=$urlencodetag$'>$tag$</a> ");
}

The Result

You can see the result of my efforts right here on GeekZilla, if you're interested in using or enhancing this code, feel free to download the example application linked from the bottom of this article.

I hope my code makes your life a bit easier, if nothing else I hope it inspires you to add a cloud to your site... doesn't just have to be for tags

Author Paul Hayman

Paul is the COO of kwiboo ltd and has more than 20 years IT consultancy experience. He has consulted for a number of blue chip companies and has been exposed to the folowing sectors: Utilities, Telecommunications, Insurance, Media, Investment Banking, Leisure, Legal, CRM, Pharmaceuticals, Interactive Gaming, Mobile Communications, Online Services.

Paul is the COO and co-founder of kwiboo (http://www.kwiboo.com/) and is also the creator of GeekZilla.

Comments

Brendan said:

Excellent article, I'm just starting out and it was really easy to follow!

11/Feb/2007 13:54 PM

benny said:

hi,

is it ok to use css code on my app ?

the implimintation of my cloud generation is bit different but i like the css.

benny@formlogix.com

15/Feb/2007 15:30 PM

tom@aeteam.co.uk said:

I created something similar for something to do at one wet afternoon.

http://cloudsearch.aeteam.co.uk

You can download the control I wrote at

http://www.cloudsearch.aeteam.co.uk/CloudControl.aspx

27/Feb/2007 15:37 PM

babu said:

hi,

Thank you...good article...

13/Apr/2007 12:16 PM

Adam said:

Hello,

I have the tag cloud implimented on a website and it was working perfectly fine but then I added in more stuff to the cloud and not the spacing is all off and their is huge gaps in the formatting. It has something like this

test test test test test test test test test test test

etest text starts here and

auto returns down here test test test test test tes test test test test test test test test test test

Is there a way to fix this?

Thanks

06/Jun/2007 22:24 PM

phayman said:

It sounds like something that could be fixed in CSS.. if you could send me a link to an example I'd have a better idea how to fix it.

Regards,

Paul

11/Jun/2007 14:11 PM

Vortex Ministry said:

This code saved me hours of time. Works great and was easy to setup. Thanks for posting this.

To see it in action visit: <a href="http://www.GreatFaithStories.com">www.GreatFaithStories.com</a>

21/Sep/2007 16:16 PM

eduardo said:

01/Nov/2007 08:39 AM

alex said:

Thank a lot... works Just Fine...

29/Nov/2007 05:56 AM

sunwangji said:

Thanks for sharing!! A couple more shiny nuggets for my collection.this article make me consider that the tag cloud is not in the "sky". (e.g can be learnt).

30/Nov/2007 09:46 AM

Tim said:

I love the article and to output is great. But do you have any advice or maybe another article about adding tags to an item like an article and having the new tags post to a DB? If you did that would make this all the better.

Thanks for the article, great work.

14/Dec/2007 00:40 AM

Bart said:

Hello, Its a really nice looking tag cloud, how would i change the code to grab the cloud data from a mssql database? i have the query that returns each tag and a number of how many times its referenced, im just having trouble lniking it in to the code. Thanks and keep up the good work. Bart

26/Feb/2008 19:07 PM

phayman said:

Bart,

this example is using mssql database.. are you sure you're binding the page?

If you're still having problems, post the specific problem and I'll help you out.

Paul

11/Mar/2008 07:57 AM

Gonçalo Caeiro said:

Hi,

Great stuff

We are trying to find some code to provide same base features but with a twist to be scalable:

a) Load and keep all structure in memory so that we can realtime feed the clients, just like Digg. So it's like having a hashing table that stores the Keys and Counts. (no database access and sql parsing)

b) A syndicated real time stripped communication protocol above TCPIP with clients where we tramsmit only the differential information to keep bandwidth low.

In two weeks time we'll post the code if we can't find, or if you want to help just drop me a note.

12/Jul/2008 09:39 AM

Gonçalo Caeiro said:

Hi Paul,

Just FYI in case of interested to you and/or the community I just created a bounty on:

http://www.bountyup.com//bounty/Provide+a+scalable+tag+cloud+feeder

Thanks, Gonçalo (goncalo.caeiro@gmail.com)

12/Jul/2008 10:55 AM

Arvind said:

Thanks for this article, really helpful.

08/Feb/2009 23:04 PM

Fabio said:

this feature is very good.

congratulations for excellent article.

Fabio Rodrigues

www.jmssolucoes.com.br

18/Feb/2009 12:38 PM

Nick said:

Fantastic article Paul. Nice & clean approach.

30/Mar/2009 12:23 PM

Sam said:

I implemented this and it works fine.

I made these tags link buttons and they are added to asp panel

however there is word spacing problem (my tags are words like this...

greener & cheaper as one link button or one tag)

 they are not bound to the tag cloud. how can i fix this spacing problem

words appear like greener & Cheaper...

27/May/2009 16:43 PM

mark said:

zip for source doesnt exist ?

04/Jun/2009 22:12 PM

mrpu said:

Another very clean implementation is http://thetagcloud.codeplex.com. It takes care of grouping, counting and rendering of tags. It also provides filtering capabilities.

10/Jun/2009 13:14 PM

Miguel said:

Hi all!

Unfortunatelly, this zip file is not available.

Can you fix it, please?

thanks

14/Jul/2009 10:25 AM

gautam_r84 said:

Hi paul can u please fix the attachment , its not here

03/Feb/2010 10:01 AM

gautam_r84 said:

fix the attachment , much needed.

Thanks

03/Feb/2010 10:03 AM

James cooper said:

Can some body please tell me the sigificance of Contenttag.GetCloudData().Tables[0] in the code

16/Feb/2010 10:11 AM

Eduard said:

Hi! The Address for download is down... Can you Fix? Please!

25/Feb/2010 17:39 PM

phayman said:

Hi, I'll see what I can do.. sorry about that

31/Mar/2010 13:59 PM

Andrew said:

Awesome work, really looks cool and works well, you learn something new every day, thanks.

03/Apr/2010 07:59 AM

Mike said:

Great article,

22/Oct/2010 23:26 PM

Jeroen said:

Ik krijg hier een foutmelding, hoe los ik dit op?

CloudLiteral.Text = GetCloud(ContentTag.GetCloudData().Tables[0],

        "<a class='weight$weight$' href='" + browseUrl + "?tag=$urlencodetag$'>$tag$</a> ");

Error 2 The name 'ContentTag' does not exist in the current context L:\vsPROJECT\Visual Studio 2008\WebSites\Login\TagCloudData.aspx.cs 100 38 L:\...\Login\

25/Nov/2010 04:52 AM

hulya said:

There is no attachment Could you upload it again, please?

Where ContentTag is implemented in this code?

21/Dec/2010 13:58 PM

hulya said:

Hi Paul,

Could you reupload source code, please?

Thank you in advance.

27/Dec/2010 12:30 PM

Hopeto said:

Thanks for the article. It's working and i will try to implement it my project www.nagalandpost.com.

The source code download is missing, so i copy pasted your code on the browser

Thanks again.

10/Jan/2011 08:38 AM

eaga said:

hai mr.paul,

        i need much more info about cloud formation using c#.wil u pls upload it.............
08/Feb/2011 08:51 AM

Nagendran said:

Excellent article. This helps to who needs such kind of stuff themselves. I created something similar for tagcloud control with few lines of javascript code in http://www.metatagclouder.com

20/Dec/2011 11:43 AM

Joe E said:

Hello,

Nice articlae, Can you repost the zip file of the source code? TIA

15/Feb/2012 22:39 PM

Add Comment

Name
Comment
 

Your comment has been received and will be shown once it passes moderation.