Exploring Data Visualization with an HTML 5 Canvas based Tag Cloud powered by JSON

0

Tag Clouds are an interesting way to present information. They very quickly convey an impression of the relative sizes of a substantial number of entities and they do so in an attractive, almost playful way. By making the tags clickable, tag cloud can also be used to quickly navigate or drill down (filter). The use of color or font style – in addition to the size of the tags – allows the presentation of additional dimensions in the data. Data visualization is one of my areas of personal interest and the tag cloud is very much part of that. Widgets for rendering tag clouds are omnipresent and have been so for many years. However, with recent browser evolution and the quick adoption of HTML 5, I was interest in the current state of affairs. I wanted to find a simple to use component to present a tag cloud, independent of specific JavaScript libraries or server side frameworks. In short a component that I can easily reuse in web applications – from rich client/thin server style to ADF and APEX.

imageA little Googling brought me in contact with the wordcloud2 component, created by Tim Chien and available at http://timdream.org/wordcloud2.js/. This component fits the bill perfectly: it renders a tag cloud based on a simple JavaScript list of tags. I can style the tag cloud in various ways. The tags can react to hovering as well as clicking. The component has no external dependencies and runs in all current browsers. It uses the HTML 5 Canvas tag for client side rendering the tag cloud.

In this article I will show how to use the wordcloud2 component to integrate a tag cloud in a web page. The example I am using is a cloud with countries – the tag weight derived from the country’s population size.

A second tag cloud is used for filtering: it shows continents and clicking a continent filters the countries cloud to display only countries in that continent. A little JSON manipulation is applied (to read and parse the country data from JSON document). An interesting JavaScript trick with arrays is demonstrated along the way – a for each and a filter function, very similar to the new Java 8 SE Lambda expressions and Streams API.

My first step with the wordcloud2 component was simply downloading the source from GIT Hub. This resulted in a file wordcloud2.js. This library can be imported into any web page with a simple script element: <script type=”text/javascript” src=”mywordcloud2.js” />.

The component also requires a Canvas element in our web page. It will paint the tag cloud on the canvas element. Note: a page can contain multiple tag clouds, each in their own canvas element. The id  of the target canvas is passed in the call to wordcloud2 for a specific tag cloud.

The simplest webpage plus tag cloud that we can create is coded like this:


<meta http-equiv="Content-Type" content="text/html; charset=windows-1252" />

<script type="text/javascript" src="wordcloud2.js"></script><script type="text/javascript">// <![CDATA[
window.onload = function () {               options= { list : [ ["Apple", "23"],["Pear", "53"],["Grape", "13"],["Pineapple", "93"],["Melon", "123"]] };               WordCloud(document.getElementById('fruitBasket'), options);           };
// ]]></script>
<h1>Fruity Tag Cloud</h1>
<div id="container"></div>

The essential parts of this snippet are:
- script element that includes the wordcloud2.js component library
- the canvas with id velue fruitBasket, initially empty
- the call in the onload function to the WorldCloud function that references the canvas the id and passes an object that contains a list with tags (note: each tag consists of an array with two values, the first the text to be displayed for the tag and the second the relative weight).

The tag cloud looks like this when opened in the browser:

image

Stepping it up a little – with more complex data, loaded from a JSON source

In my self defined challenge of creating a tag cloud based on country data, I had an obvious need for country data, preferably in JSON format. The site http://peric.github.io/GetCountries/  provides exactly that: the ability to downloaded a self configured file (XML, JSON or CSV) with country data. This helped me prepare the file countries.txt that you can download under Resources.

In my web page, I now have to import and parse this JSON resource into a JavaScript data structure. This is of course a very common challenge, one that I wanted to deal with through standard browser capabilities and not Jquery or other frameworks. That is quite simple with today’s browsers:

var countries;
var xhr = new XMLHttpRequest();
// data from : <a href="http://peric.github.io/GetCountries/">http://peric.github.io/GetCountries/</a>

xhr.open('GET', 'countries.txt');
xhr.onload = function (e) {
countries = JSON.parse(this.response);
}
xhr.send();

This code assumes somewhat naively that the countries.txt document that contains the JSON based countries data is available from the same server and directory as the web page itself. Using XMLHttpRequest(), the file is loaded and subsequently (in the onload callback function) parsed into the countries variable.

The next step I need to take is the transfer of the countries to the options object that forms the input of the WordCloud function. This is part of a function repaintCountryTagCloud that is invoked to do exactly what the name indicates.


function repaintCountryTagCloud() {

var totalPopulation = 0;

// determine the total population of all the countries
countries.forEach( function (element, index, array) {
totalPopulation = totalPopulation + parseInt(element.population);
}
);
// set some of the options of the tag cloud to a non default value
options = {
weightFactor : 1, fontFamily : 'Finger Paint, cursive, sans-serif', color : '#f0f0c0', hover : window.drawBox, backgroundColor : '#ff1f00'
};
var list = [];

// add all countries to the list for the tag cloud
countries.forEach( function (element, index, array) {
var size = 450 * parseInt(element.population) / totalPopulation;
list.push([element.countryName, size]);
}
);
options.list = list;
// refresh the wordcloud in the continentCanvas with the options (and list) as just specified
WordCloud(document.getElementById('countryCloudCanvas'), options);
}//repaintCountryTagCloud

Notice how the total population over all countries is calculated using a forEach call back function on the countries Array. This is fairly advanced feature in JavaScript (see an introduction here: http://www.tutorialspoint.com/javascript/array_foreach.htm). This mechanism is very similar to what is introduced in Java 8 through Lamba expressions and the Streams API on Collections. The essence is: specifying a callback function with a predefined signature – three input parameters – that is to be invoked for each of the elements in the array. This same forEach construction is used to compile the list of all country based tags.

This function can now be called from the onload callback function introduced before:

xhr.onload = function (e) {
countries = JSON.parse(this.response);
// paint country cloud for the first time
repaintCountryTagCloud();
}

The HTML page that contains all of this, also contains a canvas with the id countryCloudCanvas:

<meta http-equiv="Content-Type" content="text/html; charset=windows-1252" />

<script type="text/javascript" src="wordcloud2.js"></script><script type="text/javascript">// <![CDATA[
... als JavaScript sources introduced overhead
// ]]></script>
<h1>Tag Cloud - based on relative population sizes</h1>
<div id="myCanvasContainer"></div>

The page when loaded in a browser looks like this:

image

Introducing a second tag cloud for filtering/downdrilling

As I mentioned in the opening section of this article, a tag cloud can be used to allow users to navigate, filter/drilldown or otherwise initiate actions. I want to show next how using the wordcloud2 component and its support for callback-on-click, it is easy to extend the page with the countries tag cloud with a tag cloud that displays continents and responds to a click on a continent click with a filter action on the countries tag cloud.

This continents tag cloud is defined statically; its tag are all the same size at this point. Associated with this tag cloud is a JavaScript call back function that assesses the continent that was selected and acts upon that. Several things were added to the page – in terms of HTML and JavaScript:

- a global array with continents, a global variable that holds the currently selected continent
- a new canvas into which the continents tag cloud is loaded
- a JavaScript function initializeContinents() that initializes the continents and the continents tag cloud; this function is invoked from the Window.load function
- a call back function called callbackOnClickContinent() that is invoked by wordcloud2 whenever a tag is clicked upon; this function sets the currently selected continent and invokes the repaintCountryTagCloud
- modifications in function repaintCountryTagCloud() to make it only include those countries in the tag-cloud that are in the currently selected continent

The most important new element is the call back function, defined in the continents tag cloud like this:

....
options.list = list;
// when a continent is clicked upon in the tag cloud, the countries-cloud should be painted for that continent
// the callback listener to handle this is set here
options.click = callbackOnClickContinent;
options.rotateRatio = 0.3;

// paint the continents-cloud
WordCloud(document.getElementById('myCanvas'), options);
}

And implemented like this:


function callbackOnClickContinent(item, dimension, event) {
// find continent with continentName = item
var selectedContinent;
for (var i = 0;i < continents.continents.continent.length;i++) {
var name = continents.continents.continent[i].continentName;
if (name == item[0]) {
// found
selectedContinent = continents.continents.continent[i];
break;
}
}// for
if (selectedContinent != null) {
// then set current continent to continent.continent
currentContinent = selectedContinent.continent;
// then repaint country tag cloud
repaintCountryTagCloud();
}

}

Download the resources to inspect all other JavaScript code and to try out the two linked tag clouds.

Potential next steps

The implementation as it is right now has an issue: if one tag is much larger than all other tags, it is not displayed at all (try North America, South America and Oceania and try to find the big boys in those continents). This could be something to dive into.

The wordcloud2 component also supports callback functions to set color for the tags (useful for example to have color derived from surface area) and on hover.

 

Resources

Try out the Continents & Countries Tag Clouds sample page  tagcloud.html.

Download the sources discussed in this article:tagcloudSources.zip.

For the country data in JSON format http://peric.github.io/GetCountries/
For the WordCloud2 JavaScript component https://github.com/timdream/wordcloud2.js/blob/master/API.md
For help on the filter method on Arrays https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
For help on the forEach method on Arrays https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
For help on loading the JSON data in an AJAX request http://www.html5rocks.com/en/tutorials/file/xhr2/
Not needed, but certainly interesting: JSONP for loading JSON data from other sites (cross site referencing)

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

Leave a Reply