Adding a Tag Cloud to my Hugo blog

#  1. Tags Template Page

The first step is to figure out which template is generating the HTML for the /tags/ page. Depending on the complexity of your theme, this is more or less difficult. The theme I’m using as a base (Vienna) is structurally quite simple, so it didn’t take too long: themes/vienna/layouts/_default/terms.html This terms.html) template file is relatively simple, so it serves as a good starting point for building your own.

If you are having more trouble, make sure you remember that “tags” is one of the taxonomies in Hugo. Hugo’s template lookup order for taxonomy pages (e.g. tags, categories etc.) is specified here: https://gohugo.io/templates/lookup-order/#examples-layout-lookup-for-taxonomy-pages

#  2. Generate a Javascript object from all tags

To be able to use the tags data interactively in the page, I outputted it into a Javascript object from Hugo. You can use either a Map or an Array, depending on what you want to do with it:

1
2
3
4
5
6
7
8
<script>
let tagMap = new Map();
let tagArray = new Array();
{{ range $key, $value := .Data.Terms }}
    tagMap.set("{{ $key }}", {{ len $value }});
    tagArray.push([{{ $key }}, {{ len $value }} ]);
{{ end }}
</script>

#  3. Use wordcloud2.js to generate a wordcloud

While searching around for a project that already implements an algorithm for generating a wordcloud, I came across the Word Cloud Generator website (try it online). This seemed to be exactly what I was looking for. Luckily, the core functionality of this website has been packaged into a library with a straightforward API and no dependencies - great!

https://github.com/timdream/wordcloud2.js

To get started, I added the following HTML snippet to my terms.html template:

1
2
3
4
5
6
7
8
<canvas id="tag-canvas" width="600" height="300" style="border: 1px solid red;"></canvas>
<script src="/js/wordcloud2.js"></script>
<script>
    WordCloud(document.querySelector("#tag-canvas"), {
      list: tagArray,
      // weightFactor: 8,
    });
</script>

You might need to play with the weight factor to get the right size of the words for them to fill up the entire area (depending on the amount of tags and their weights). See also issue 153 for directions on how to get the right size.

The result was pretty fancy! Screenshot 1: Work-in-progress wordcloud

In general, have a look at the all the available options (API) for generating the word cloud.

#  4. Generate HTML elements instead of Image

While the result above looks nice, it was completely non-interactive due to it being a <canvas> element (essentially just an image). Depending on the use case, this might be fine, but I wanted the tags in the cloud to actually be clickable links, to make it easy to explore my blog. After all, this is the world wide web with hypertext.

Fortunately, Wordcloud2.js not only offers the option of generating a wordcloud in a canvas element, but also the generate the words as regular span elements (text). To use this functionality, we only need to supply a <div> element (instead of <canvas>) to the constructor:

1
2
3
4
5
6
<div id="tag-wrapper" style="width: 100%; height: 400px;"></div>
<script>
    WordCloud(document.querySelector("#tag-wrapper"), {
        list: tagArray,
    });
</script>

Make sure you specify non-zero width and height for the wrapper container, otherwise the library cannot calculate the size of the words and ends up outputting nothing at all.

Now the code generates <span> elements inside the wrapper container, but it’s still not interactive (i.e. no links).

To convert the tags (inside <span>) into functioning links (<a>), I found this helpful comment on issue 133. It suggests a solution using jQuery, so I just reimplemented the same functionality with vanilla Javascript:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const tagCanvas = document.querySelector("#tag-wrapper"); // select your element to draw in
WordCloud(document.querySelector("#tag-wrapper"), {
    list: tagArray,
    classes: "tag-cloud-item", // add a class to each tag element so we can easily loop over it
});

tagCanvas.addEventListener('wordcloudstop', function (e) {
    // loop over all added elements (by class name)
    document.querySelectorAll('.tag-cloud-item').forEach(function (element) {
        const word = element.innerHTML;
        element.innerHTML = `<a href="/tags/${word}/" style="color: inherit;">${word}</a>`;
    });
});

First, I’m telling Wordcloud2.js to add the CSS class “tag-cloud-item” (this is an arbitrary name) to each word element it is generating (line 4). Then, once the library finished creating the elements (event “wordcloudstop”, line 7), I iterate over all elements with this class (line 9) and convert the text inside the span into an <a> link (line 10 and 11). The color is also set to inherit to preserve the colorful look of the wordcloud, otherwise all links would have the same color (line 11).

#  Done!

Go check it out live: Jack’s Blog Tags

Screenshot 2: Final interactive Wordcloud