Tag & Categories Cloud for Hugo

Tags and categories are a great choice for organizing content.
In this post I’d like to show how to layout & program a tag cloud for hugo, a static site generator.

When creating a new article in the content folder, don’t miss to enter the “tags” and “categories” taxonomy information. This very little extra information added to every page enables you to implement quite a lot of extra functionality - like a tag cloud.

The tag cloud is rendered on the fly and based on the taxonomy which gives your static web site an even more dynamic attitude.

Implementation

As the template does not take any arguments into account (for the moment), the tag cloud code could be used:

  • either as “partial” (/partials/tag_cloud.html)
  • or as a “shortcode” (/shortcodes/tag_cloud.html).

The html output produced for both versions might be identical but of course both files can be customized independently.

Remember:

  • shortcodes can be embedded directly in the markdown text which is a great choice to create feature rich content with fairly less effort.
  • partials are used to beautify the layout of the template itself, and are related to the pre-defined layout of the theme.

Source code

As mentioned the code in this use-case is identical for both use-cases, either as shortcode or as partial:

{{/*
	--- shortcode/widgets/tag_cloud.html
	Credits:
		This code was inspired by the academic theme.
		Thanks to: George Cushen
	Purpose:
		This function does not take any arguments into account.
		Read all "tags" and "categories" from the site's taxonomies
		and create a test cloud which can be used to navigate the site.
	
	(c) 2020, Johann Oberdorfer - Engineering Support | CAD | Software
		www.johann-oberdorfer.eu
	This source file is distributed under the MIT license.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the MIT License for more details.
*/}}


{{ $fontSmall := .Page.Params.design.font_size_min | default 0.9 }}
{{ $fontBig := .Page.Params.design.font_size_max | default 2.5 }}
{{ $fontDelta := sub $fontBig $fontSmall }}

{{/* read "tags"... */}}
{{ $data := .Site.Taxonomies.tags.ByCount }}

{{/* read "categories"
     and add this information to the data array... */}}

{{ $categories := .Site.Taxonomies.categories.ByCount }}
{{ if ne (len $categories) 0 }}
	{{ $data = $data | append $categories }}
{{ end }}


{{/* output goes here...  */}}

{{ if ne (len $data) 0 }}

	{{/* Warning:
			Hugo's `Reverse` function appears to operate in-place,
			hence the order of performing $max/$min matters.
	*/}}

	{{ $max := add (len (index $data 0).Pages) 1 }}
	{{ $min := len (index ($data).Reverse 0).Pages }}
	{{ $delta := sub $max $min }}
	{{ $fontStep := div $fontDelta $delta }}

	<div class="row" style="background-color:HoneyDew;padding:5px">
	<div class="heading text-center" style="margin-top: 5px;">

	<!-- unused for the moment:
	<div class="heading text-center" style="margin-top: 5px;">
		<h4><i class="icon-line-flag"></i> Categories: & <i class="icon-tag2"></i> Tags: </h4>
	</div>
	-->

	<div class="col-12 text-center">

		<div class="tag-cloud">
		{{ range $item := (sort $data ".Page.Title" "asc") }}

			{{ $tagCount := len $item.Pages }}
			{{ $weight := div (sub (math.Log $tagCount) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }}
			{{ $fontSize := add $fontSmall (mul (sub $fontBig $fontSmall) $weight) }}

			<!-- class="button button-border button-mini button-border-thin button-blue button-circle" -->

			{{ if ne (upper .Page.Title) "ARCHIVED" }}
				<a href="{{ .Page.RelPermalink }}" style="font-size:{{ $fontSize }}rem">
					{{ .Page.Title | upper }}
					<span style="color: LightGrey; font-size:{{ $fontSmall }}rem"> ({{ $tagCount }}) </span>
				</a>
			{{ end }}

		{{ end }}
		</div>

	</div>
	</div>
	</div>
{{ end }}

Output

After copying the code and saving it to: shortcodes/widgets/tag_cloud.html the following line needs to be inserted into the markdown text wherever it’s suitable:

{{/* < widgets/tag_cloud > */}}

This will produce a tag cloud which e.g. could look like the following.
In our case, the tag cloud is based on the taxonomy of this site and therefore is “live” and fully functional.


Tag cloud with style

Recently I stumbled upon some css code to beautify the output even more. The template code stays the same, with some more embedded css it becomes more fancy & responsive.

Here is the modified source code...

{{/*
	--- shortcode/widgets/tag_cloud_with_style.html
	Credits:
		This code was inspired by the academic theme.
		Thanks to: George Cushen
	Purpose:
		This function does not take any arguments into account.
		Read all "tags" and "categories" from the site's taxonomies
		and create a test cloud which can be used to navigate the site.
	
	(c) 2020, Johann Oberdorfer - Engineering Support | CAD | Software
		www.johann-oberdorfer.eu
	This source file is distributed under the MIT license.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the MIT License for more details.
*/}}


{{ $read_tags := true }}

{{ $fontSmall := .Page.Params.design.font_size_min | default 0.9 }}
{{ $fontBig := .Page.Params.design.font_size_max | default 1.9 }}
{{ $fontDelta := sub $fontBig $fontSmall }}

{{/* read "categories"... */}}
{{ $data := .Site.Taxonomies.categories.ByCount }}

{{/* read "tags"
     and add this information to the data array... */}}

{{ if $read_tags }}

	{{ $tags := .Site.Taxonomies.tags.ByCount }}

	{{ if ne (len $tags) 0 }}
		{{ $data = $data | append $tags }}
	{{ end }}

{{ end }}


{{/* output goes here... */}}

{{ if ne (len $data) 0 }}

	<style>
		.tag-cloud-tags {
			display: block;
			margin: 0;
			list-style-type: none;
		}
		.tag-cloud-tags a {
			display: inline-block;
			padding: 0.3rem;
			line-height: 1em;
			word-break: break-word;
			white-space: normal;
			text-decoration: none;
			border-bottom: 1px solid LightGrey;
		}
		.tag-cloud-tags a:nth-child(5n+1) { transform: rotate(-2deg); }
		.tag-cloud-tags a:nth-child(3n+3) { transform: rotate(1deg); }
		.tag-cloud-tags a:nth-child(3n+4) { transform: rotate(-3deg); }
		.tag-cloud-tags a:nth-child(5n+5) { transform: rotate(3deg); }

		/* underline animation from left */

		.hover-animate-from-left {
		  display: inline-block;
		  vertical-align: middle;
		  -webkit-transform: perspective(1px) translateZ(0);
		  transform: perspective(1px) translateZ(0);
		  box-shadow: 0 0 1px rgba(0, 0, 0, 0);
		  position: relative;
		  overflow: hidden;
		}
		.hover-animate-from-left:before {
		  content: "";
		  position: absolute;
		  z-index: -1;
		  left: 0;
		  right: 100%;
		  bottom: 0;
		  background: #2098D1;
		  height: 2px;
		  -webkit-transition-property: right;
		  transition-property: right;
		  -webkit-transition-duration: 0.8s;
		  transition-duration: 0.8s;
		  -webkit-transition-timing-function: ease-out;
		  transition-timing-function: ease-out;
		}

		.hover-animate-from-left:hover:before,
		.hover-animate-from-left:focus:before,
		.hover-animate-from-left:active:before {
		  right: 0;
		}
	</style>

	{{ $max := add (len (index $data 0).Pages) 1 }}
	{{ $min := len (index ($data).Reverse 0).Pages }}
	{{ $delta := sub $max $min }}
	{{ $fontStep := div $fontDelta $delta }}

	<div class="row" style="background-color:HoneyDew;padding:5px">
	<div class="heading text-center" style="margin-top:5px">

	<div class="col-12 text-center">

		<div class="tag-cloud-tags">
		{{ range $item := (sort $data ".Page.Title" "asc") }}

			{{ $tagCount := len $item.Pages }}
			{{ $weight := div (sub (math.Log $tagCount) (math.Log $min)) (sub (math.Log $max) (math.Log $min)) }}
			{{ $fontSize := add $fontSmall (mul (sub $fontBig $fontSmall) $weight) }}

			{{ if ne (upper .Page.Title) "ARCHIVED" }}
				<a href="{{ .Page.RelPermalink }}" style="font-size:{{ $fontSize }}rem" class="hover-animate-from-left">
					{{- .Page.Title | upper -}}
					<span style="color: LightGrey; font-size:{{ $fontSmall }}rem"> ({{ $tagCount }}) </span>
				</a>
			{{ end }}

		{{ end }}
		</div>

	</div>
	</div>
	</div>
{{ end }}

And here is the styled tag cloud version:

That’s all.