DEV Community

Carly Ho ๐ŸŒˆ
Carly Ho ๐ŸŒˆ

Posted on

Dynamically Sizing Text for a Tag Cloud Effect

It's common to see data presented in a tag cloud formatโ€”which is to say, a chunk of keywords or terms displayed inline, with each term sized to show its relative importance or frequency out of all the existing terms.

tag cloud of New York Stock Exchange symbols

A lot of times we see these tag clouds built into content management systems or generated via plugins, but how do we make something like this if we want to do it from scratch? In this tutorial I'll walk through how I built this feature for my own website, to display the relative amounts of time I've worked with different technologies.

Setting up

I tried to keep the markup pretty simple:

<ul>
  <li data-year="2001">HTML</li>
  <li data-year="2002">CSS</li>
  <li data-year="2003">PHP</li>
  <li data-year="2010">Javascript</li>
  <li data-year="2012">Ruby</li>
  <li data-year="2010">Python</li>
  <li data-year="2017">Node.js</li>
  <li data-year="2010">MySQL</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

It's a simple unordered list. The only thing here that's special is that each <li> has a data-attribute, data-year, which denotes the year I started working with that particular technology. If you were creating a tag cloud for the number of articles with a given tag on your blog, you might instead call the data-attribute data-count, and set it to the number of articles with that tag.

Moving on to the styles, again keeping it pretty simple. Mostly I stripped out the default formatting, aligned the content to the center, and set list-items to display: inline-block so they coalesce into one block of text.

ul {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  text-align: center;
}

ul li {
  margin: 0 5px;
  display: inline-block;
  vertical-align: middle;
}
Enter fullscreen mode Exit fullscreen mode

The Scripting

First, let's wrap everything in an event handler for the window's load event, just to make sure everything's in place before we start applying our font-sizing. I'm using a bit of jQuery here to speed the process along, but there's no reason you couldn't do it in vanilla Javascript.

$(window).on('load', function() {
  //...
});
Enter fullscreen mode Exit fullscreen mode

If your font sizes change at certain screen size breakpoints, you may also want to re-trigger this on resize as well as load.

Now that we've got that done, we need to get the range of numbers represented in our tag cloud.

  var nums = $('ul li').map(function() {
    return parseInt($(this).data('year'));
  }).get();

  var maxVal = Math.max(...nums);
  var minVal = Math.min(...nums);
Enter fullscreen mode Exit fullscreen mode

The map function iterates over every list item and returns the value of the data-year attribute, parsed as an integer; the .get() method formats the output into an array.

We then pass the numbers into Javascript's Math.max and Math.min functions to get the largest and smallest values, respectively. (nums preceded by the ... to denote that it should be read as an array.)

Now, we'll calculate the sizing for the fonts.

  var currentDate = new Date();
  var currentYear = currentDate.getFullYear();
  var baseFont = $('ul li').css("font-size");
  var fontsplit = baseFont.match(/([0-9\.]+)\s?([a-z\%]+)/);
  var minFont = parseInt(fontsplit[1]);
  var maxFont = parseInt(fontsplit[1])*3;
  var fontUnits = fontsplit[2];
Enter fullscreen mode Exit fullscreen mode

In this case, we're getting the value of the current year since we want to have the program do the math on how long I've worked with a technology. If you're getting something like a number of blog posts or inventory count instead, you can skip that step!

Then we get the base font size from the CSS. It comes with the units, so I've inserted a regular expression to match a number and a unit abbreviation. The current font size becomes the minimum font size for the tag cloud, assigned to minFont; the maximum font size is the minimum times three. You can adjust these to tasteโ€”for example, if you want your minimum to be 80% of the base font size, you'd set minFont = parseInt(fontsplit[1])*.8.

Now it's time to actually apply the font sizes!

  $('ul li').each(function() {
    var itemYear = parseInt($(this).data('year'));
    if (itemYear) {
      var num = currentYear - itemYear;
      var fontSize = (((maxVal - itemYear )/(maxVal - minVal)) * (maxFont - minFont)) + minFont;
      $(this).css("font-size", fontSize+fontUnits);
      $(this).attr("title", (currentYear - itemYear)+" years");
    } else {
      // if we can't determine the year, just set it to 90% size
      $(this).css("font-size", (minFont*.9)+fontUnits);
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

First it performs a quick check to make sure that data-year is actually set before trying to access it. Then we do a little math, subtracting the value of data-year from the current year to determine the years experience (again, this step is skippable if you're not trying to determine years since).

We determine the font size by figuring out what percentage the current number is between the minimum and maximum value, multiplying that by the difference between maxFont and minFont, and then adding that to minFont. We then set the css font-size value to that number and tack the units back on at the end.

Also, just to make sure that the information is represented in a way that's accessible to non-visual media, we set the list item's title attribute to "[N] years."

Final Product

All of this adds up to a result that looks a bit like this!

You can also see a live data version on my personal website if you scroll down to the bottom.

Top comments (0)