Tagcloud Code

Steve asked me about my tagcloud and the code needed to return to make it work. Thank you Steve - I've been waiting for someone to ask about it give me the excuse to say that I think my tagcloud looks pretty good and a darn site better than some of the sorry excuses for tagclouds out there. And it's only taken 6 months for someone to ask about it. Thank you readers - 6 months I've been on tenterhooks and in the end it took Steve to rescue me from my anguish. Anyway, back to the tagcloud...

The tagcloud is implemented as an HttpHandler which, when called, returns some JavaScript to create the HTML rendition of my tagcloud. In my blog skin I have the following line:

 <script type="text/javascript" src="https://mikeo.co.uk/tagcloud/tagcloud.ashx"></script>

Browsing direct to that URL will give you something a bit like this:

 <!--

drawTagCloud('<div id="tagcloud">' + 
'<em>' + '<em>' + '<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Ajax&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Ajax&p=1">' +
'ajax</a> ' +'</em>' + '</em>' + '<em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=ASP.NET&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=ASP.NET&p=1">' +
'asp.net</a> ' +'</em>' + '</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=ATLAS&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=ATLAS&p=1">' +
'atlas</a> ' +'</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Charity&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Charity&p=1">' +
'charity</a> ' +'</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Event&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Event&p=1">' +
'event</a> ' +'</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Launch&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Launch&p=1">' +
'launch</a> ' +'</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=live+writer&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=live+writer&p=1">' +
'live writer</a> ' +'</em>' +
'<em>' + '<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Marathon&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Marathon&p=1">' +
'marathon</a> ' +'</em>' + '<em>' + '<em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Microsoft&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Microsoft&p=1">' +
'microsoft</a> ' +'</em>' + '</em>' + '</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=MSDN&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=MSDN&p=1">' +
'msdn</a> ' +'</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Office&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Office&p=1">' +
'office</a> ' +'</em>' + '<em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Office+2007&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Office+2007&p=1">' +
'office 2007</a> ' +'</em>' + '</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=Plugin&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=Plugin&p=1">' +
'plugin</a> ' +'</em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=SharePoint&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=SharePoint&p=1">' +
'sharepoint</a> ' +'</em>' + '<em>' + '<em>' + '<em>' +
'<a href="https://blogs.msdn.com/mikeormond/search.aspx?q=VISTA&p=1" mce_href="https://blogs.msdn.com/mikeormond/search.aspx?q=VISTA&p=1">' +
'vista</a> ' +'</em>' + '</em>' + '</em>' + '</div>' );

function drawTagCloud(s){ document.write(s); }

// -->

In other words, the HttpHandler generates the JavaScript for a very simple function drawTagCloud() that writes to the document object and calls that function with a big lump of html.

For the HttpHandler itself, I use a simple helper class, TagInfo, to represent a "tag" and its frequency of occurence. TagInfo looks a bit like this:

 public class TagInfo : IComparable
{
  private int posts;
  private string tagName;

  public int Posts
  {
    get { return posts; }
    set { posts = value; }
  }

  public string TagName
  {
    get { return tagName; }
    set { tagName = value; }
  }

  public int CompareTo(object obj)
  {
    if (obj is TagInfo)
    {
      TagInfo t = (TagInfo)obj;
      return tagName.CompareTo(t.tagName);
    }

    throw new ArgumentException("Object is not a TagInfo");
  }
}

I need to know the tag string and the number of posts so I can emphasize the terms that appear most often. I've simplified the handler code below just to try and keep it short by removing the caching (in ProcessRequest()). I've also removed the body of the GetTagList() method - that's the piece you'll need to customise to return your own List<TagInfo> representing the terms and frequency of occurence for your tagcloud. (I also removed all the Using statements just to save some space).

 <%@ WebHandler Language="C#" Class="TagCloud" %>

//Using Statements Go Here

public class TagCloud : IHttpHandler
{
  public void ProcessRequest(HttpContext context)
  {
    List<TagInfo> taglist = GetTagList();
    cachedTags = CreateTagMarkup(taglist);
    context.Response.ContentType = "text/javascript";
    context.Response.Write(cachedTags);
  }

  public bool IsReusable
  {
    get { return false; }
  }

  private static string CreateTagMarkup(List<TagInfo> taglist)
  {
    taglist.Sort();
    int totalTagCount = 0;
    foreach (TagInfo t in taglist)
      totalTagCount += t.Posts;

    StringWriter sw = new StringWriter();

    sw.WriteLine("<!--");
    sw.WriteLine(@"drawTagCloud('<div id=""tagcloud"">' + ");

    foreach (TagInfo t in taglist)
    {
      int fontSize = Math.Min(15 * t.Posts / totalTagCount, 5);
      AddEmphasis(sw, fontSize, true);
      sw.Write(@"'<a href=""https://blogs.msdn.com/mikeormond/search.aspx?q=" +
        t.TagName.Replace(" ", "+") + @"&p=1"">' + ");
      sw.Write("'" + t.TagName.ToLower() + "</a> ' +");
      AddEmphasis(sw, fontSize, false);
    }

    sw.WriteLine("'</div>' );");
    sw.WriteLine("function drawTagCloud(s){ document.write(s); }");
    sw.WriteLine("// -->");

    sw.Flush();
    string js = sw.ToString();
    sw.Close();
    return js;
  }

  private static void AddEmphasis(StringWriter sw, int fontSize, bool IsOpeningTag)
  {
    for (int i = 0; i <= fontSize; i++)
    {
      string s = IsOpeningTag ? string.Empty : "/";
      sw.Write(@"'<" + s + @"em>' + ");
    }
  }

  private static List<TagInfo> GetTagList()
  {
    List<TagInfo> taglist = new List<TagInfo>();

    //
    //Create your taglist in here
    //

    return taglist;
  }
}

Now it was a while ago I did this but I'm pretty sure I had a browse around at other people's implementations for inspiration. In particular I'm sure I looked at Technorati's scripts to see how they did it (it was when I was adding a Technorati script that I got the urge to add a tagcloud too). Hopefully it's pretty clear. For the caching I simply cached the value of cachedTags and checked the cache when ProcessRequest() is called. The only other thing I did cache-wise is to set a 1 hour cache duration if taglist.Count >0 and 5 mins otherwise. My "tag provider" has a tendency to go "dark" from time to time and if that happens, I don't want to wait an hour for the tagcloud to be refreshed.

Technorati tags: tagcloud, httphandler, asp.net, javascript