kosta.apostolou.ca

How to Create a TagCloud Using LINQ and ASP.NET

by Kosta on Feb.11, 2008, under Software Development

One of the projects I’ve been working on lately is an overhaul of casamia.ca (which I plan on releasing in the next couple weeks). The goal is to give the site a fresh look and feel and add some cool new features. A part of the new vision is to make the site a little more web 2.0-ish, get some user interaction, and all that fun stuff. I’d also like to update it from .NET 1.0 all the way up to the recently released .NET 3.5. One of the features I wanted to implement was a TagCloud where users can click on a tag and it will bring up a list of products in the database that have been tagged accordingly.

This blogpost will show you how you can implement your own tagcloud using LINQ and ASP.NET 3.5.

Data Model

Let’s start with our simple data model:

TagCloud Data Model

One thing I’d like to point out here that is not depicted in the diagram above is that there is a unique key constraint on the ProductTags table on ProductId and Tag columns. This will ensure we never end up having duplicate tags for a given product. Aside from that, this is just a simple one-to-many relationship of Products to ProductTags. Any time we want to add a tag to a Product, we simply add an entry to the ProductTags table.

For the sake of having some data to play with, go populate your Products and ProductTags tables, making sure to have the same tag appear multiple times so we can have a useful tagcloud. To do this, look for some commonalities amongst your products.  For example, let’s say our Products table contains menu items you would find in a restaurant. Let’s also say that a subset of the menu contains the following products: Chicken Souvlaki, Chicken Pita, Chicken Caesar Salad, and Greek Salad. A couple good tags for these products may be “Chicken” and “Salad”. In this case, you would insert 3 entries into your ProductTags table, with all 3 using the tag “Chicken” for your chicken products. You could also add 2 more entries for “Salad” to tag your salad products. You get the idea….

Generate the LINQ to SQL Classes

Back in Visual Studio now, add a new “LINQ to SQL Classes” item to your website. Let’s call it MyDatabaseName.dbml.  Next, drag and drop the Products and ProductTags tables into the designer. I realize the details I’m providing in this step are not complete, but the steps are pretty simple if you’ve done it before. Although these details are out of scope for this blogpost, the good news is that there are a number of blogposts out there that dive into detail on this topic. If you want more info, check out Scott Guthrie’s blog which is a great source of information. Take a look at this post to get you started.

TagCloud: Take 1 (Baby steps…)

Once we have our data model and database populated, our first step is to simply display all our tags (we’ll worry about font sizing a little later). To do this, let’s use one of my new favourite controls that was shipped with ASP.NET 3.5: the ListView control. Personally, I’m a big fan of implementing something like this using a web user control, so I’m going to go ahead and create a TagCloud.ascx user control. Then I’ll add a ListView control to the user control. Here is the code in my TagCloud.ascx file:

Next, we need to populate that ListView with some data. Here is magic in our code-behind:

The only thing interesting here is the the LINQ query syntax with the use of anonymous types. If you have some SQL experience, this is the equivalent to the following query:

SELECT Tag, COUNT(*) FROM ProductTags GROUP BY Tag;

which simply returns a list of unique tags along with the number of times the tag appears in the ProductTags table. The TagFrequency property of our anonymous type will come in handy in the next step. For now, we’re just showing off ;)

At this point, we should be generating a list of hyperlinks. Here’s what mine looks like at this point:

TagCloud Without Different Text Sizes

Tag Cloud: Take 2

Up until now, we haven’t really done anything other than dumping out a list of tags from our database. BOOORING!!

Here comes the interesting part. We have to figure out a way to assign some sort of “weight” to each tag so that we know the font-size to use when rendering the tag. Let’s update our BindTagCloud() method as follows:

The first part of this method (lines 1-11) remains unchanged. At line 12, the maxTagFrequency variable figures out the frequency in which the most frequently used tag appears in the ProductTags table. We then use this value against every tag in our database (line 18) to determine its weight relative to the most used tag. In other words, if our most used tag is used 50 times, then its weight will be 100. If another tag in our database is used 15 times, then its weight would be 30.

The only left thing left to do now is to adjust the font-size of the tag hyperlink accordingly.  To do this, let’s go back to our user control and add a style attribute to the anchor tag:

Now, we just have to write the GetTagSize method (line 6) that will return the size of the font. Let’s add the following code to our code-behind:

That’s it! That should produce a tagcloud with varying font-sizes as desired. Here’s how mine looks:

TagCloud With Different Text

Improvements

The purpose of this blogpost is to show you how to implement a simple tagcloud. Ideally, you shouldn’t be injecting LINQ code in your code-behind, but rather be querying a different layer in your application. This depends on the architecture of your application. Also, rather than populating a font-size, you could get fancy and use CSS to control the look of each different tag weight.  That said, for the purposes of this demonstration, this should get the job done.

kick it on DotNetKicks.com

:, , , , ,

9 comments for this entry:
  1. codepolice.net(new comment)

    Pingback from codepolice.net

    Codepolice.net » Blog Archive » Create a tag cloud with limited amout of tags in ASP.NET and LINQ

  2. Varun(new comment)

    Hello,
    I really liked your sample on Tag Clouds but since i am kinda new at using LINQ i was wondering if it was possible if you can explain how this part works

    <a href=”Products.aspx?Tag=”
    style=”font-size:” >

    Does the “Weight” refer to the “Weight” in
    “var tagCloud”?

    Because i get this error

    DataBinding: ‘f__AnonymousType12[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]’ does not contain a property with the name ‘Weight’.

    meaning it cant find “Weight”. Can you give me some advice on this?

  3. Kosta(new comment)

    Hey Varun,

    Yes, “Weight” refers to the “Weight” in “var tagCloud”.

    I’m not sure why it can’t find it without looking at your code. I would double check to make sure it’s not a silly mistake like using “weight” in your LINQ query and then referencing it as “Weight” in the aspx page (ie. it’s case-sensitive).

    Hope that helps,

    Kosta

  4. Varun(new comment)

    Well i pretty much followed what you did.

    I created two separate projects, one which holds my Default.aspx file and one that holds the cloud.ascx file.

    There is a reference to the project containing the cloud.ascx and its code behind in the project which contains the Default.aspx file and its code behind.

    I purposely spelled weight as “WEight”. This is my cloud.aspx file

    <a href=”Products.aspx?Tag=”
    style=”font-size:” >

    No Tags Found.

    And this is its corresponding code behind file

    using System;
    using System.Collections;
    using System.Configuration;
    using System.Data;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Xml.Linq;
    using System.Data.Linq.Mapping;
    using TagClouds;

    namespace LinqTrial2
    {
    public partial class cloud : System.Web.UI.UserControl
    {
    protected void Page_Load(object sender, EventArgs e)
    {
    TagcloudDataContext tc = new TagcloudDataContext();

    var tagSummary = from t in tc.ProductTags
    group t by t.Tag into tagGroup
    select new
    {
    Tag = tagGroup.Key,
    TagFrequency = tagGroup.Count()
    };
    int maxTagFrequency = (from t in tagSummary select t.TagFrequency).Max();

    var tagCloud = from t in tc.ProductTags
    group t by t.Tag into tagGroup
    select new
    {
    Tag = tagGroup.Key,
    WEight = (double)tagGroup.Count() / maxTagFrequency * 100
    };

    tagCloudList.DataSource = tagSummary;
    tagCloudList.DataBind();

    }
    public string GetTagSize(double weight)
    {
    if (weight >= 99)
    return “xx-large”;
    else if (weight >= 60)
    return “x-large”;
    else if (weight >= 40)
    return “large”;
    else if (weight >= 20)
    return “medium”;
    else
    return “small”;
    }
    }
    }

    So it doesnt seem like a case sensitive mistake. Am i missing a reference to “var tagCloud”?

  5. Varun(new comment)

    Ok my previous post seems to be missing the rest of the code for cloud.ascx so here it is :

    <a href=”Products.aspx?Tag=”
    style=”font-size:” >

    No Tags Found.

  6. Varun(new comment)

    Thats odd, i dont think the post wants to take in the rest of the code, i apologize for posting so much, i ll post the relevant data here

    <a href=”Products.aspx?Tag=”
    style=”font-size:” >

    //================================
    <a href=”Products.aspx?Tag=”
    style=”font-size:” >

  7. Kosta(new comment)

    Varun – you’re binding against tagSummary but should bind against tagCloud.

    This line:
    tagCloudList.DataSource = tagSummary;

    should read:
    tagCloudList.DataSource = tagCloud;

    The error message you got back is basically telling you there’s no “Weight” in what you’re binding against (in this case tagSummary) which makes sense.

    That should fix things up, but let me know if it doesn’t…

  8. Varun(new comment)

    Thank you for pointing that out, i realized it was a careless mistake. lol! And yes it did fix it up! Thanks!

  9. David(new comment)

    Thanks, nice and simple.

Leave a Reply

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact me so I can take care of it!

Check out my other blogs

Some more places I ramble at...

Archives

All entries, chronologically...