Good charts turn raw data into information that users can grasp easily. Great charts go beyond that. They tell engaging stories and invite exploration and discovery. This blog describes the process of implementing of one of the greatest charts I have ever seen using the Wijmo library.
Get the sample: WealthHealth sample
The Chart
We will be implementing a bubble chart that was originally developed by GapMinder, and made famous by a memorable 2006 TED talk by Hans Rosling. The chart shows the evolution of life expectancy, income, and population in 178 countries over a 210-year period.
The same chart was later implemented by Mike Bostock using his D3 library.
The chart is amazing because it includes an animation feature that shows how each country evolved over time in terms of life expectancy, income, and population. As you watch the animation, you can track individual countries or see overall patterns emerge. The final result tells a fascinating story about the Wealth and Health of Nations.
Here is a static version of Mike Bostock’s version of the chart:
Mike Bostock's Wealth and Health of Nations
The Data
Accurate data is at the core of all good charts. Collecting and updating this data is usually the hardest part of creating the charts. Fortunately for us, this part of the job was done by GapMinder and Mike Bostock. We use the same data file in Mike’s sample.
The data is stored in a one-megabyte json file containing information in this format:
{
"name": "United States",
"region": "America",
"income": [
[ 1800, 1912.62 ],
...
[ 2009, 41256.08 ]
],
"population": [
[ 1820, 9980510 ],
...
[ 2008, 303824646 ]
],
"lifeExpectancy": [
[ 1800, 39.41 ],
...
[ 2009, 79.43 ]
]
}
The income, population, and life expectancy data are represented by arrays of year/value pairs. The data is sparse, especially prior to 1950, and the sample uses linear interpolation to fill in the gaps.
The Wijmo Implementation
Implementing this interactive, dynamic chart using Wijmo and the AngularJS framework was surprisingly easy.
The Controller
The first step was to declare a controller with the time span, the current year, and the data. The data was loaded using Wijmo’s httpRequest method and stored in a CollectionView:
// get reference to the app, create controller
var app = angular.module('app');
app.controller('appCtrl', function ($scope) {
// nation data
$scope.yrMin = 1800;
$scope.yrMax = 2009;
$scope.year = $scope.yrMin;
$scope.data = new wijmo.collections.CollectionView(null, {
sortDescriptions: [
new wijmo.collections.SortDescription('yearPopulation', false)
],
filter: function (item) {
return item.population.length > 1 &&
item.income.length > 1 &&
item.lifeExpectancy.length > 1;
}
});
// get data
// https://bost.ocks.org/mike/nations/nations.json
wijmo.httpRequest('nations.json', {
success: function (xhr) {
$scope.data.sourceCollection = JSON.parse(xhr.response);
$scope.toggleAnimation(); // start animation when data is loaded
}
});
});
The “data” CollectionView is initialized without any data, but with a sort description and a filter.
The sort description ensures countries with large populations are plotted first, so they don’t obscure smaller countries.
The filter is used to remove items without enough data. In our data set, there are two countries in this category: Mayotte and Tokelau. This leaves 266 countries on the chart.
The “toggleAnimation” method is responsible for changing the “year” variable between its current value and the last year available, 2009. It was implemented using Wijmo’s animate method:
// animate the year
$scope.animating = 0;
$scope.toggleAnimation = function () {
if ($scope.animating) {
clearInterval($scope.animating);
$scope.animating = null;
} else {
var min = ($scope.year < $scope.yrMax - 10) ? $scope.year : $scope.yrMin,
max = $scope.yrMax,
duration = 10000 * (max - min) / ($scope.yrMax - $scope.yrMin);
$scope.animating = wijmo.animate(function (pct) {
$scope.year = Math.round(min + (max - min) * pct);
$scope.$apply();
if (pct == 1) {
$scope.animating = null;
$scope.$apply();
}
}, duration);
}
}
The last interesting part of the controller is the function that watches the “year” variable and updates the statistics to reflect the current year:
// update data for current year
$scope.$watch('year', function () {
$scope.updateData();
});
$scope.updateData = function () {
var year = $scope.year,
items = $scope.data.items;
for (var i = 0; i < items.length; i++) {
var item = items[i];
item.yearIncome = interpolate(item.income, year);
item.yearPopulation = interpolate(item.population, year);
item.yearLifeExpectancy = interpolate(item.lifeExpectancy, year);
}
$scope.data.refresh();
}
When the year changes, the code scans all the data items and sets “year*” properties to values obtained by interpolating the original data to get the current year’s value. After the loop, the code calls the “refresh” method on the CollectionView so any bound controls will be notified and updated.
At this point, the data is loaded and ready to be plotted. So let’s move on to the view.
The View
The view was implemented using Wijmo’s Angular directives.
It starts with a linear gauge used to show and select the current year, and a button to toggle the animation:
<wj-linear-gauge
value="year"
min="{{yrMin}}" max="{{yrMax}}"
is-read-only="false"
is-animated="false"
thumb-size="30">
<wj-range wj-property="face" thickness="0.08"></wj-range>
<wj-range wj-property="pointer" thickness="0.08"></wj-range>
</wj-linear-gauge>
<button class="btn btn-success" ng-click="toggleAnimation()">
<span ng-class="{ 'glyphicon-stop': animating … }"></span>
</button>
The gauge’s “value” property is bound to the “year” variable in the controller. Because the gauge’s “isReadOnly” property is set to false, it can be used to edit the year.
The button element uses the “ng-click” attribute to toggle the animation, and the “ng-class” attribute to show a “play” or “stop” glyph depending on the current state.
Finally, we get to the chart:
<wj-flex-chart
items-source="data"
chart-type="Bubble"
options="{ bubble: { minSize: 5, maxSize: 100 } }"
item-formatter="chartItemFormatter"
binding-x="yearIncome"
tooltip-content="<b>{item.name}</b><br/>{yearPopulation:g1,,} million people">
<div class="watermark">
{{ year }}
</div>
<wj-flex-chart-series
binding="yearLifeExpectancy,yearPopulation">
</wj-flex-chart-series>
<wj-flex-chart-axis
wj-property="axisX"
title="income per capita (inflation-adjusted US dollars)"
major-grid="false" axis-line="true" min="300" max="100000" log-base="10">
</wj-flex-chart-axis>
<wj-flex-chart-axis
wj-property="axisY"
title="life expectancy (years)"
major-grid="false" axis-line="true" min="20" max="85" major-unit="10">
</wj-flex-chart-axis>
<wj-flex-chart-legend position="None">
</wj-flex-chart-legend>
</wj-flex-chart>
The “itemsSource” property is set to the “data” variable in the controller. This is the sorted and filtered CollectionView that contains all the items. When the current year changes, the collection and the chart are automatically updated.
The “chartType” property is set to “Bubble”, and the “options” property is used to set the minimum and maximum size of the bubble symbols. The “itemFormatter” property points to a function used to customize the color of each bubble to reflect the item’s region. In our data set, countries are divided into six regions: Sub-Saharan Africa, South Asia, Middle East & North Africa, America, and Europe & Central Asia. The function is trivial, so we won’t list it here. Please refer to the sample’s source code for details.
The “tooltipContent” property is set to a string that represents an HTML template. In our example, the tooltip will show the country name in bold, followed by its population in the current year, scaled to millions.
Within the chart element, we added a div element that displays the current year as a watermark. The element is positioned behind the chart’s series and axes, and formatted as a large transparent number aligned to the bottom-right of the chart.
The chart series element has a “binding” property that specifies the properties of the data items that represent the Y value and bubble size. The X value was specified by the “bindingX” attribute on the chart element itself.
The axis elements specify the titles and scaling for the X and Y axes. By default, the scaling is calculated automatically, but in this case we wanted to keep it constant so the axes remain fixed as the current year changes.
The explanation is longer than the markup. The final result looks very similar to the original sample: Wijmo version of the chart
Final Touches
When I finished the first version of this sample, one of my colleagues made two really cool suggestions:
- Use opacity in order to improve the display of clustered bubbles. With semi-transparent bubbles, clusters with more overlapping bubbles appear as darker areas, improving the quality of the chart, and
- Turn on chart selection so users can pick a country they are interested in and track it through the animation.
I loved both suggestions, because they improve the sample a lot, and also because they were really easy to implement. Basically, all I had to do was set the chart’s “selectionMode” property and add a couple of CSS rules.
This image shows the final version of the sample, this time tracking Mexico as it moved across the screen: Use opacity to improve the display, and country selection to filter results
Conclusion
I love charts, and had been planning to implement a Wijmo version of this amazing chart for quite some time. When I finally got around to doing it, I was amazed at how easy it turned out to be. It took less than a day to put the whole thing together.
Of course, this was possible only because the idea and the data were made available by GapMinder and Mike Bostock, and I am very grateful to them.
Comparing this implementation to the original is hard, because that was a larger sample, written with Adobe Flash. The original version takes a while to load, but when it’s loaded it offers lots of options including the ability to show different indicators not included in this version.
Compared to the D3 implementation, the animation in our sample is not as smooth. That is because D3 creates the bubbles once, as SVG elements, and then animates smoothly them as they change values. Our sample, on the other hand, re-creates all the bubbles at each animation step. The animation works fine, but it is not as smooth.
The advantage of the Wijmo implementation is that it is extremely general and simple. It leverages the CollectionView class to provide sorting, filtering, and binding notifications as most Wijmo samples do. The markup used to create the chart is also standard, identical to the markup we use to create static business charts. It relies completely on the FlexChart’s performance to create the illusion of animation.
Comparing Wijmo to D3 would be like comparing apples and oranges. Wijmo is a library of UI components for business Web applications. It includes grids, charts, gauges, and input components that are powerful, easy to use, and easy to customize. D3 is a library for manipulating documents based on data. It excels at creating custom data visualizations like the one described here. In general, creating charts using D3 requires more knowledge, talent, and dedication than using a charting control.
Whatever tool you choose, I hope you find these samples and charts as interesting and inspiring as I do.
View the Sample
Try out the sample for yourself: WealthHealth sample