JavaFun with D3.js: Data Visualization Eye Candy with Streaming JSON

Fun with D3.js: Data Visualization Eye Candy with Streaming JSON

By Tomomi Imura

D3-1

What You Are Going to Do

In this tutorial, you will learn how to:

  • Create a static bubble chart by using d3.js
  • Bind streaming JSON data to the chart
  • Live-update and animate the bubble chart

1. Create a Static Bubble Chart

A bubble chart is a type of chart that displays data in bubble-like circles. The size of each circle corresponds with the value of data. Although a bubble chart is not the most precise type of chart, you can pack hundreds of bubbles in an area, and the representation is informative, fun, and visually appealing.

Let’s get started. Include d3.min.js in your HTML file. Then, define a layout.

1.1. Use D3’s Graphical Layout

To draw a bubble chart, we create a pack layout by using the d3.layout.pack() object. (01.js)

var diameter = 600;

var svg = d3.select('#chart').append('svg')
   .attr('width', diameter)
   .attr('height', diameter);

var bubble = d3.layout.pack()
   .size([diameter, diameter])
   .padding(3);   // padding between adjacent circles
   // new data will be loaded to bubble layout
   .value(function(d) {return d.size;})

1.2. Work with JSON Data

For now, we are using this fake data, which is similar to the actual streaming JSON we will use in this tutorial. Assume the data came from the PubNub data center, and each data set represents a country, and access volume from the country. (02.js)

var data = {"countries_msg_vol": {
   "CA": 170, "US": 393, "CU": 9, "BR": 89,
   "MX": 192, ..., "Other": 254
}};

Then, let’s customize this raw data to be used in the pack layout. The pack layout is part of D3’s family of hierarchical layouts and, by default, D3 assumes that the input data is an object with a children array, so it is convenient to return what the object looks like, such as {children: [an array of objects]}. (03.js)

function processData(data) {
   var obj = data.countries_msg_vol;

   var newDataSet = [];

   for(var prop in obj) {
      newDataSet.push({name: prop,
         className: prop.toLowerCase(), size: obj[prop]});
   }
   return {children: newDataSet};
}

The className values are to be used to colorize each SVG circle by country, defined in CSS. (04.js)

.ca, .us {fill: #DF4949;}
.uc, .br, .mx {fill: #E27A3F;}
.other {fill: #45B29D;}
...

1.3. Enter Data into the Layout

We are loading the tailored data into the layout object’s nodes() function, which automagically populates graphical data (size of each circle and positions) with a set of standard attributes, so all the circles will fit nicely in a chart. (05.js)

var nodes = bubble.nodes(processData(data))
   // filter out the outer bubble
   .filter(function(d) { return !d.children; });

Then, use the generated layout calculations to display in SVG. (06.js)

var vis = svg.selectAll('circle')
   .data(nodes, function(d) { return d.name; });

vis.enter().append('circle')
   .attr('transform', function(d) { return 'translate('
      + d.x + ',' + d.y + ')'; })
   .attr('r', function(d) { return d.r; })
   .attr('class', function(d) { return d.className; });

Awesome! You’ve created a bubble chart!

See the Pen D3 Bubble Chart (with Static Data) by Tomomi Imura at CodePen.

2. Make It Dynamic with Streaming JSON

We are ready to replace the static JSON with real live JSON from PubNub data streams. First, include the PubNub JavaScript libraries in your HTML to begin. (07.html)

<script src="http://cdn.pubnub.com/pubnub.min.js"></script>

We are using a chunk of predefined set of data here for the exercise, so let’s initialize the API with the existing channel. (08.js)

var channel = 'rts-xNjiKP4Bg4jgElhhn9v9';

var pubnub = PUBNUB.init({
   subscribe_key: 'e19f2bb0-623a-11df-98a1-fbd39d75aa3f'
});

2.1. Subscribe the Live Data

To retrieve the live data, you simply use the PubNub subscribe() API. (09.js)

pubnub.subscribe({
   channel: channel,
   callback: drawBubbles(message)
});

Once you successfully retrieve the data from the stream, call the callback function to draw the chart. (10.js)

function drawBubbles(message) {
   // place the code from Step 1.3
}

Now, every time a new set of data comes in, new bubbles are displayed. However, they keep being added on top of the previous chart. This looks pretty funky, and is not what we want! We need to fix this.

D3-2
Figure 2: Overlapping bubbles

Instead of entering new data to a new layout, what we need to do is update the existing layout with new data. In the next step, we are going to modify the d3 data entry.

3. Live-Update and Animate the Bubbles!

Okay. Let’s bind data to elements correctly.

3.1. Assign Each Node with a Unique Name

D3 uses the enter, update, and exit patterns to join data to the DOM. In Step 1.3, you have to enter() the initial data. To make the node updateable, you need to assign a name to each node. D3 takes a key function as a second argument to the data() method. Modify the code to assign a unique field name: (11.js)

var vis = svg.selectAll('circle')
   .data(nodes, function(d) { return d.name; });

3.2. Create Chained Transitions

Also, in Step 1.3, we celled data(), enter(), and append() sequentially. To enter new data to the existing nodes, we are going to update them. This way, each assigned bubble circle updates its size and position correctly, instead of creating a new one with new data.

D3-3
Figure 3: The D3 Bubble Life Cycle

To make smooth transitions between old and new data, apply a transition only to the updating nodes, and not entering nodes. The trick is to create transition on the updating elements before the entering elements because enter().append() merges entering elements into the update selection. You can learn more on this topic in this Exit, Update, Enter tutorial.

Some bubbles may be a null in an upcoming data set, and need to be removed. We use exit() and remove(). Also, let’s give an opacity (1 to 0) transition to create a complete sequence of chained transitions. (12.js)

var duration = 200;
var delay = 0;

// update - This only applies to updating nodes
vis.transition()
   .duration(duration)
   .delay(function(d, i) {delay = i * 7; return delay;})
   .attr('transform', function(d) { return 'translate(' + d.x + ','
      + d.y + ')'; })
   .attr('r', function(d) { return d.r; })

// enter
vis.enter().append('circle')
   .attr('transform', function(d) { return 'translate(' + d.x + ','
      + d.y + ')'; })
   .attr('r', function(d) { return d.r; })
   .attr('class', function(d) { return d.className; })
   .style('opacity', 0)
   .transition()
   .duration(duration * 1.2)
      .style('opacity', 1);

// exit
vis.exit()
   .transition()
   .duration(duration + delay)
   .style('opacity', 0)
   .remove();

In this example, we are transitioning the position and radius of each bubble upon updating the data, as well as the opacity upon entering and exiting. Moreover, by using the delay method along with transition, we are making the animation effect more interesting. Tweak the values and see what works the best.

I hope you enjoyed the tutorial. If you would like to explore this demo, see the entire source code of the demo on github!

# # #

This article was contributed by the author, Tomomi Imura, Senior Developer Evangelist at PubNub.

Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.

Latest Posts

Related Stories