Tag clouds in Erlang with ErlyWeb

| | Comments (3)

Updated This article was updated to incorporate several changes to the tagcloud.erl module. Thanks Ulf!

There are plenty of ways to render tag clouds and Erlang is no exception. With these two small modules you render tag clouds in ErlyWeb. The logic is pretty simple and could be applied to any other tempting system with little effort.

Listing 1-1: The tagcloud.erl module.

-module(tagcloud).
-export([create_cloud/1, tag_size/4]).

create_cloud([]) -> [];
create_cloud(Tags) ->
    AlphaSortedTags = lists:keysort(2, Tags),
    Counts = [C || {C, _} <- Tags],
    Max = lists:max(Counts),
    Min = lists:min(Counts),
    Diff = Max - Min,
    Dist = Diff / 3,
    [{Name, Count, tagcloud:tag_size(Count, Min, Max, Dist)} || {Count, Name} <- AlphaSortedTags].

tag_size(Count, Min, Max, Dist) ->
    if
        Count == Min -> smallest;
        Count == Max -> largest;
        Count > Min + (Dist * 2) -> large;
        Count > Min + Dist ->  medium;
        true -> small
    end.

In the tagcloud module, the main function used to generate the cloud is create_cloud/1. That function takes a list of tuples representing tags and their counts. The list is then sorted and the largest and smallest values extracted from the list. It is here that we also calculate the spread and distance between tags.

For each tag the size of the tag is calculated. Based on the max, min and distance values, an atom representing the tag size is associated with the tag and a new list is returned. At first this was done with a separate function that iterated through the list of tags and determined the size. Ulf Wiger pointed out a better way to do this is through a simple list comprehension. Now, the tags are sent through a list comprehension that calls a function to determine the tag size and the final list is returned to the function caller.

1> RawTags = [{15, "erlang"}, {9, "perl"}, {12, "erlyweb"}, {17, "yaws"}].
[{15, "erlang"}, {9, "perl"}, {12, "erlyweb"}, {17, "yaws"}].
2> Tags = tagcloud:create_cloud(RawTags).
[{"erlang",15,large},
 {"erlyweb",12,medium},
 {"perl",9,smallest},
 {"yaws",17,largest}]

Now we can feed in the processed list of tags into the displaytagcloud/1 function as part of the tagcloudt.et template file. The template does nothing more than apply the style that has already been calculated based on the size atom by iterating through the list of tags and associating a css class to each.

Listing 1-1: The tagcloudt.et template.

<%@ display_tag_cloud(Tags) %>
<% tag_style() %>
<div style="padding: 5px;"><% tag_cloud(Tags) %></div>
<%@ tag_cloud(Tags) %><% tag_cloud_tag(Tags) %>
<%@ tag_cloud_tag([]) %>
<%@ tag_cloud_tag([{Name, Count, Size} | Tags]) %>
 <a href="http://blog.socklabs.com/tag/<% Name %>" class="<% Size %>Tag"><% Name %></a><% tag_cloud_tag(Tags) %>
<%@ tag_style() %>
<style>
.smallestTag { font-size: xx-small; }
.smallTag { font-size: small; }
.mediumTag { font-size: medium; }
.largeTag { font-size: large; }
.largestTag { font-size: xx-large; }
</style>

Just add the template file with the rest of your ErlyWeb templates. You can call the display_tag_cloud from any of your templates.

...
Tags:<br/>
<% tagcloudt:display_tag_cloud(Tags) %>
...

It couldn't be easier.

3 Comments

Ulf Wiger said:

Nice, Nick. A few minor comments:

Normally in recursive iterators, the recursive call is done
with an unqualified call. Sucking up a new module version in
the middle of an iteration can lead to unpredictable behaviour.
Use fully qualified calls when you deliberately want to pick
the latest version. In the middle of a computation, this is
rarely the case.

If you unpack the TagCount before calling max and min,
you will get slightly nicer code:

max([{Count,_}|T], Max) when Count > Max -> max(T, Count);
max([_|T], Max) -> max(T, Max);
max([], Max) -> Max.

Or, better yet:

Counts = [C || {C, _} <- Tags],
MaxTagCount = lists:max(Counts),
MinTagCount = lists:min(Counts),
...

You can also change the create_cloud() to:

[{Name, Count, tag_size(Count,Min,Max,Dist)} ||
   {Name,Count} <- AlphaSortedTags].

tag_size(Count, Min, Max, Dist) ->
if
Count == Min -> smallest;
...
end.

A matter of taste, perhaps.

Nick Gerakines Author Profile Page said:

Thanks for your feedback Ulf. Looking back, I can see that it was doing a lot of extra work -- simple is almost always better.

Paul Brown said:

I don't know that much about how ErlyWeb is laid out inside the Erlang VM, but uou can save yourself some cycles if you store the "state" of the tag cloud in a process and update it only when needed.

Leave a comment

About this Entry

This page contains a single entry by Nick Gerakines published on April 10, 2008 4:23 PM.

A RESTful web service demo in yaws was the previous entry in this blog.

Book in hand is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.