Data visualization, Part 2: Use D3 component layouts

Learn graphical calculations for drawing components in various arrangements

In this two-article series, learn how to use Scalable Vector Graphics (SVG) with the open source D3 JavaScript library to create data visualizations. Shapes, colors, and layouts can be of great help in making business sense out of data volumes. This article demonstrates various ways to arrange graphical components to represent your data on a canvas, using both D3's and your own calculations.

Bilal Siddiqui , Freelance consultant, XML4Java

Bilal Siddiqui is an electronics engineer, XML consultant, technology evangelist, and frequently published technical author. He is the founder of XML4Java.com, a company that is focused on simplifying e-business. After graduation in 1995 from the University of Engineering and Technology, Lahore, Bilal began designing software solutions for industrial control systems. Later, he turned to XML and built web- and WAP-based XML processing tools, server-side parsing solutions, and service applications. Since 2006, he has focused exclusively on Java™- and XML-based open source tools and solutions. A strong advocate of open source tools, he not only designs solutions that are based on them but also trains software and IT personnel at Lahore universities in using open source technologies. Bilal is the author of JasperReports 3.6 Development Cookbook (Packt Publishing, 2010).



15 July 2013

Also available in Chinese Japanese

Part 1 of this two-article series presents an overview of how SVG and D3 work together, along with some basic examples that create visualizations of browsing data for social media. This second part takes you through the steps for using different arrangements, or layouts, of graphical components in your SVG drawings. You'll learn how to use D3's powerful graphical calculations to place components on your SVG canvas, and how to combine your own graphical manipulations with D3's layouts. I also explore the use of JavaScript Object Notation (JSON) as a data format for your visualizations. The article concludes by showing how you can arrange various graphical components on a single SVG canvas using combinations of layouts.

The concepts and examples in this article build on the concepts and examples in Part 1, so be sure to read or review it before you continue. As you read Part 2, you might find it useful have both articles open side-by-side in your browser so you can refer to the images in Part 1. See Download to get the Part 2 sample code.

Introducing D3's graphical layouts

I start by building on what you learned in Part 1 about D3's features.

Recall that Figures 1 and 2 in Part 1 differ only in the way the circles are arranged, and that the transform attribute value in the JavaScript code for those figures (shown in Listings 2 and 4 in Part 1) calculates the relative position of each circle's center.

Graphical calculations to determine the relative positions of individual components are called layouts in D3 terminology. D3 provides various powerful and reusable layouts. The arrangement of arcs and chords in Part 1 is one of them. It's handy to know how to use D3's layouts both alone and in combination with a bit of your own graphical calculations.

The D3 pack layout, shown in Figure 1, is a pack of circles within a bigger circle. (As in Part 1, the circles depict week 1 page views.) In this article, I demonstrate the use of the pack layout along with some of my own calculations.

Figure 1. Pack layout that shows circles within a bigger circle
The D3 pack layout: circles within a bigger circle

The circles in Figure 1 are the same as those circles generated in Part 1, but in a different arrangement. The difference results from the graphical calculations done by D3's pack layout.

Without the outer circle, the layout in Figure 1 looks like Figure 2:

Figure 2. Pack layout without the outer circle
Pack layout without the outer circle

Part 1's Figure 1 uses a simple layout that is based on my own simple graphical calculations, whereas Figure 2 here uses D3's pack layout. If you combine the graphical calculations that are done by the two layouts, you can visualize several weeks of popularity data, as shown in Figure 3:

Figure 3. Three weeks of popularity data in one graphic
Three weeks of popularity data in one graphic

You can also combine Figure 3 with the circles-within-circles arrangement of Part 1's Figure 3 to depict several weeks of popularity data together with user-interaction data on one canvas, as shown in Figure 4:

Figure 4. Combining multiple layouts to show several weeks of popularity data and user-interaction data
Multiple layouts combined to show several weeks of popularity data and user-interaction data

Now I'll describe the SVG and D3-based JavaScript code for displaying Figures 1 through 4.


Drawing a pack of circles

Listing 1 shows the SVG code to draw the pack of circles that are shown in Figure 1:

Listing 1. SVG code that draws the pack of circles in Figure 1
<?xml version=";1.0"; standalone=";no";?>
<!DOCTYPE svg PUBLIC ";-//W3C//DTD SVG 1.1//EN"; 
";http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";>

<svg xmlns=";http://www.w3.org/2000/svg"; version=";1.1";
width=";1000"; height=";1000";>

<g>
   <circle 
      r=";250"; 
      style=";fill: #da70d6;";
      transform=";translate(250,250)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=";translate(250,250)";>
         26733
   </text>
</g>

<g>
   <circle 
      r=";91.28916405756645";
      style=";fill: #0000ff;";
      transform=
       ";translate(172.869706621408,159.71405581800542)";>
   </circle>
   <text
      x=";-10"; fill=";grey"; 
      transform=
       ";translate(172.869706621408,159.71405581800542)";>
            7057
   </text></g>

<g>
   <circle
      r=";94.00415374552455";
      style=";fill: #ffd700;";
      transform=
        ";translate(120.90301328980084,337.57095449403647)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=
       ";translate(120.90301328980084,337.57095449403647)";>
            7483
   </text>
</g>

<g>
   <circle
      r=";66.53756320431968";
      style=";fill: #008000;";
      transform=
        ";translate(271.77788076862765,282.7036851574789)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=
        ";translate(271.77788076862765,282.7036851574789)";>
            3749
   </text>
</g>

<g>
   <circle
      r=";67.39284824138821";
      style=";fill: #ff0000;";
      transform=
        ";translate(405.70829221433553,282.7036851574789)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=
        ";translate(405.70829221433553,282.7036851574789)";>
            3846
   </text>
</g>

<g>
   <circle
      r=";73.68747158608183";
      style=";fill: #800000;"; 
      transform=
        ";translate(337.8448727920879,159.01774033373852)";>
   </circle>
   <text
      x=";-10";
      fill=";grey";
      transform=
        ";translate(337.8448727920879,159.01774033373852)";>
            4598
   </text>
</g>

</svg>

You can see that the root <svg> tag in Listing 1 contains six <g> child tags, each of which has a pair of <circle> and <text> tags. The first <g> tag draws the large outer circle, which I've included only to give you a view of D3's pack layout. The outer circle isn't needed in the sample application, so I make it invisible by changing its color from da70d6 (a color called orchid) to fffff (white), resulting in Figure 2 instead of Figure 1. Each of the remaining five <circle> tags draws one of the five circles of Figure 2, each of which represents the popularity of one social resource.

Two points are worth noting about the five <circle> tags:

  • Each <circle> tag's r attribute (the radius of each circle) represents the popularity of that circle's social resource. (The more popular the resource, the bigger the circle.)
  • The translate(x,y) values of the transform attribute position each of the circles within the pack.

The r, x, and y values completely determine the size and position of each circle. D3's pack layout calculates these three attributes for you. Listing 2 shows the JavaScript code that uses D3 to perform the graphical calculations and generate the SVG in Listing 1:

Listing 2. D3-based JavaScript code to generate pack layout of circles
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!--
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>
 -->
<script src="d3.v3.js"></script>
<!--Save d3.v3.js in the same folder-->
<script>

var views = [
  [7057, 7483, 3749, 3846, 4598],
  [ 2371, 7397, 4589, 2861, 8249],
  [ 5972, 5672, 9152, 9725, 8983],
  [ 9763,  8462,  9782, 1953, 5182],
  [ 9567,  1571,  2895, 2783, 1874],
  [ 2371, 7397, 4589, 2861, 8249]
		];
var width = 1000, height = 1000;
var colors = [
 "white",//"orchid", if you want to see the outer bigger circle.
            "blue",
            "gold",
            "green", 
            "red", 
            "maroon"
		];
var week = 0;

//Step 1: Make JSON representation of popularity data.
var viewsJSON = getJSONForOneWeekOfPopularityData(0);

//Step 2: Pass on JSON to pack layout and get graphical 
//calculations in return.
var pack = d3.layout.pack()
    .size([500, 500])
    .value(function(d) { return d.popularity; });
var packCalculations = pack.nodes(viewsJSON);

//Step 3: Use calculations to generate SVG.
var svg = d3.select("body").append("svg")

.attr("width", width)
   .attr("height", height)

   .selectAll("g").data(packCalculations).enter();
var g = svg.append("g");
g.append("circle")
  .attr("r", function(d){return d.r})
  .style("fill", function(d, i){return colors[i%6];})
  .attr("transform", function(d,i){return "translate(" + d.x + "," + d.y + ")"});
g.append("text")
  .attr("x", -10)
  .attr("fill", "grey")
  .text(function(d){return d.value})
  .attr("transform", function(d,i){return "translate(" + d.x + "," + d.y + ")"});

function getJSONForOneWeekOfPopularityData(week){
var viewsJSONString = 
  "{ \"name\": \"week" + week + "\", \"children\": [" ;
  for (var count=0;
           count<5;
           count++   ){
  viewsJSONString += 
    "{ \"name\": \"" + colors[count+1] + " \", \"popularity\": " 
    + views[week][count] + " }   ";

  if (count<4) viewsJSONString += ",";

  }
  viewsJSONString += "]}"
  return JSON.parse(viewsJSONString);
}
</script>
</body>

You can see three steps in Listing 2:

  1. Make a JSON representation of the popularity data that D3's pack layout can use to make graphical calculations.
  2. Pass on the JSON object to the pack layout and get graphical calculations in return.
  3. Use the calculations to generate the required SVG code.

Now I'll describe each of these steps.

Step 1: Working with JSON

The pack layout is a hierarchical layout: It represents hierarchical data in the form of smaller (child) circles packed inside bigger (parent) circles. (D3 doesn't put a limit on the number of hierarchical levels; you can have parents, grandparents, great-grandparents, and so on.) The example's popularity and user-interaction data has only two levels: The parent level is the week numbers, and the child level is the popularity and user-interaction data for each week.

The pack layout doesn't work directly with JavaScript arrays. Instead, it takes a JSON representation of the data as input. The JSON data format can represent hierarchical data well and is directly supported by modern browsers.

Listing 3 shows a JSON representation of popularity data in comparison to an array representation of the same data:

Listing 3. Array and JSON representations of three weeks of popularity data
//array representation
var views = [
  [7057, 7483, 3749, 3846, 4598],
  [ 2371, 7397, 4589, 2861, 8249],
  [ 5972, 5672, 9152, 9725, 8983]
                   ];

//JSON representation
var viewsJSON = 
{
  "name": "PopularityData",
  "children": [
  {
    "name": "week1",

    "children": [
      {"name": "blue", "popularity": 7057},
      {"name": "gold", "popularity": 7483},
      {"name": "green", "popularity": 3749},
      {"name": "red", "popularity": 3846},
      {"name": "maroon", "popularity": 4598}
    ]
  },
  {
    "name": "week2",
    "children": [
      {"name": "blue", "popularity": 2371},
      {"name": "gold", "popularity": 7397},
      {"name": "green", "popularity": 4589},
      {"name": "red", "popularity": 2861},
      {"name": "maroon", "popularity": 8249}
    ]
  }
  { 
    "name": "week3",
    "children": [
      {"name": "blue", "popularity": 5972},
      {"name": "gold", "popularity": 5672},
      {"name": "green", "popularity": 9152},
      {"name": "red", "popularity": 9725},
      {"name": "maroon", "popularity": 8983}
    ]
  }
]};

Notice that the JSON representation uses name-value pairs. The name of the whole data object is PopularityData. PopularityData has three children, which are named Week1, Week2, and Week3. And each of the week children has five children that are named after the social resources. (The social resources' names are color names, as you recall from Part 1.) All of these objects are referred to as nodes; nodes without further children (that is, at the end of the tree hierarchy) are called leaf nodes. The popularity data is the value of each leaf nodes' popularity attribute.

Listing 4 shows how to make a JSON object for just one week of popularity data:

Listing 4. One week of popularity data in JSON format
var viewsJSON = 
{

    "name": "week1",
    "children": [
      {"name": "blue", "popularity": 7057},
      {"name": "gold", "popularity": 7483},
      {"name": "green", "popularity": 3749},
      {"name": "red", "popularity": 3846},
      {"name": "maroon", "popularity": 4598}
  ]
};

Listing 5 shows how to add user interaction to popularity data within Listing 3's JSON object:

Listing 5. Three weeks of popularity and user interaction data in JSON format
var viewsJSON = 
{
  "name": "PopularityData",
  "children": [
  {
    "name": "week1",
    "children": [
      {"name": "blue", "popularity": 7057, 
                       "user-interaction": 2052},
      {"name": "gold", "popularity": 7483},
                       "user-interaction": 2089},
      {"name": "green", "popularity": 3749},
                       "user-interaction": 1586},
      {"name": "red", "popularity": 3846},
                       "user-interaction": 1426},
      {"name": "maroon", "popularity": 4598}
                       "user-interaction": 2632},
    ]
  },
  {
    "name": "week2",
    "children": [
      {"name": "blue", "popularity": 2371},
                       "user-interaction": 2071},
      {"name": "gold", "popularity": 7397},
                       "user-interaction": 2190},
      {"name": "green", "popularity": 4589},
                       "user-interaction": 7214},
      {"name": "red", "popularity": 2861},
                       "user-interaction": 3782},
      {"name": "maroon", "popularity": 8249}
                       "user-interaction": 2721},
    ]
  }
  {
    "name": "week3",
    "children": [
      {"name": "blue", "popularity": 5972},
                       "user-interaction": 3076},
      {"name": "gold", "popularity": 5672},
                       "user-interaction": 3190},
      {"name": "green", "popularity": 9152},
                       "user-interaction": 4532},
      {"name": "red", "popularity": 9725},
                       "user-interaction": 3825},
      {"name": "maroon", "popularity": 8983}
                       "user-interaction": 4831},
    ]
  }
]};

You can see from Listing 5 that to add user-interaction data, you just need to add an attribute named user-interaction to each of the leaf nodes.

You can use JSON data in your JavaScript in multiple ways. One way is to include it directly in your JavaScript file. Another is to link JSON files (with a .json extension) to your page through the <script> tag. Or you can have your data in another format (such as a JavaScript array), which you can convert to JSON dynamically at run time.

Linking external JSON files with your JavaScript page can result in security issues. In contrast, arrays provide an easy and clear way to store data while you put together your JavaScript file on the server side. That's why I chose the third option: storing data in a JavaScript array and using array-to-JSON conversion logic within the same JavaScript file. With this option, you can call the array-to-JSON conversion logic when you need a JSON representation of your data.

As you can guess, to draw the layout in Figure 1, you need a JSON representation of one week of popularity data that is formatted as shown in Listing 4. The simple JavaScript function named getJSONForOneWeekOfPopularityData(), shown in Listing 6, generates a JSON object in that format:

Listing 6. Generating a JSON object from a JavaScript array
function getJSONForOneWeekOfPopularityData(week){

//Step 1: Author a string to JSON format.
var viewsJSONString = 
  "{ \"name\": \"week" + week + "\", \"children\": [" ;
  for (var count=0;
           count<5;
           count++   ){
  viewsJSONString += 
    "{ \"name\": \"" + colors[count+1] + " \", \"popularity\": " 
    + views[week][count] + " }   ";

  if (count<4) viewsJSONString += ",";

  }
  viewsJSONString += "]}"

//Step 2: Parse the string to create a JSON object.
  return JSON.parse(viewsJSONString);
}
</script>
</body>

You can see from Listing 6 that generating a JSON object is a simple two-step conversion process. First, you author a JavaScript String representation in exact accordance with the JSON format that you want to produce. You use the actual names of social resources while authoring the string representation so that it looks exactly like Listing 4. In the second step, you use the JSON.parse() method to convert the string into a JSON object. Most modern browsers support features related to JSON, so you can directly call the JSON.parse() function. I tried this article's code with Chrome Version 26.0.1410.64 m. If your applications target older browsers, you can include an https://github.com/douglascrockford/JSON-js/blob/master/json2.js library (which implements JSON functionality) in your page through a <script> tag.

Step 2: Graphical calculations with the pack layout

With the appropriate JSON object now in hand, you can pass it on to D3's pack layout to perform the required graphical calculations. Listing 7 (which repeats step 2 in Listing 2 for convenience) shows how simple it is to fetch graphical calculations from the pack layout:

Listing 7. Using D3 to perform graphical calculations for one week of popularity data in a pack of circles
//Step 2: Pass on JSON to pack layout and get graphical 
//calculations in return.
var pack = d3.layout.pack()
    .size([500, 500])
    .value(function(d) { return d.popularity; });
var packCalculations = pack.nodes(viewsJSON);

You just need to create a d3.layout.pack() object, then set the pack layout's size by calling its size() function. The size() function takes length and width as parameters, which set the dimensions within which the pack of circles are drawn. D3 internally scales the size of each individual circle according to the size of the layout.

Next, you call the value() function for the pack layout object and write your own function as the value. This function returns a value for each JSON node. While it does graphical calculations, D3 internally calls this function, once for each node, passing the node as the d parameter. You can see in Listing 7 that I return d.popularity as value of a node, thereby telling D3 to do graphical calculations that are based on popularity data. As a result, each circle in the pack is sized according to the node's popularity data.

The pack layout object is now ready to take the JSON object and do the calculations. Next, I call the layout object's nodes() function, passing the JSON object to the function call.

The nodes() function performs all the graphical calculations to fit the popularity circles into the pack layout. The result of the calculations is in the form of an array of nodes, each node representing the size and drawing position of one circle. The whole array of nodes contains all the data to draw the whole pack of popularity circles.

In a nutshell: You convert your JavaScript array into JSON and hand over the JSON to D3's pack layout. The pack layout performs graphical calculations and puts the calculations in another array (this time an array of nodes) — in Listing 7, an array named packCalculations.

For each node in the packCalculations array, D3 provides several attributes, including r, x, and y. Now you'll see how to use these attributes to author the SVG code in Listing 1.

Step 3: Using the pack layout calculations to generate SVG

After step 2, the packCalculations array has all the graphical data that you need to draw the pack of circles. Step 3 (duplicated in Listing 8) uses the calculations to generate SVG:

Listing 8. Using graphical calculations to draw circles
//Step 3: Use calculations to generate SVG.
var svg = d3.select("body").append("svg")

.attr("width", width)
   .attr("height", height)
   .selectAll("g").data(packCalculations).enter();
var g = svg.append("g");
g.append("circle")
  .attr("r", function(d){return d.r})
  .style("fill", function(d, i){return colors[i%6];})
  .attr("transform", function(d,i)
       {return "translate(" + d.x + "," + d.y + ")"});
g.append("text")
  .attr("x", -10)
  .attr("fill", "grey")
  .text(function(d){return d.value})
  .attr("transform", function(d,i)
         {return "translate(" + d.x + "," + d.y + ")"});

Most of what you see in Listing 8 is familiar from Part 1. The only difference is that the data this time comes from the packCalculations array. Don't worry about taking each of the nodes out of the array and drawing each of the circles one by one. You just hand over the packCalculations array to the magical .data(packCalculations) D3 function, which handles most of the job.

Notice the use of the d.r, d.x, and d.y attributes in Listing 8. To size and position each circle, you need these r, x, and y attributes. The d.r attribute is used to author the r attributes (radii) of the circles, while the d.x and d.y attributes are used to author the values of the transform attribute that positions each circle.

If you run the JavaScript in Listing 2, you'll see the image that is shown in Figure 2.


Combining external calculations with the pack layout

Now I'll explain how you can combine the pack layout with your own graphical calculations to draw the graphics in Figure 3. You can see that Figure 3 uses the pack layout of circles three times — once for each week — resulting in three sets of five circles each, with one set representing one week of popularity data.

Listing 9 shows the SVG code for Figure 3:

Listing 9. The SVG code to show three weeks of popularity data
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="1000" height="1000">

<g transform="translate(0,143)">
  <text x="60">Week 1</text>
  <!-- resource-popularity tags omitted.-->
</g>

<g transform="translate(320,143)">
  <text x="60">Week 2</text>
  <!-- resource-popularity tags omitted.-->
</g>

<g transform="translate(640,143)">
  <text x="60">Week 3</text>
  <!-- resource-popularity tags omitted.-->
</g>

</svg>

Listing 9 shows three <g> child tags of the root <svg> tag. I call these three <g> tags week-wrapper tags because each represents a week of data.

Each week-wrapper tag has six <g> child tags (the bigger circle plus the five popularity circles); I refer to them as resource-popularity tags. I omitted the resource-popularity tags because from Listing 1 you know what they look like.

Now look at the transform attributes of the week-wrapper tags. Their values place the first pack of circles that are centered around the point 0, 143; the second around 320, 143; and the third around 640, 143. This placement is the result of my own simple graphical calculation, which wraps the whole of pack of circles. I will now show you how.

JavaScript to generate the SVG code in Listing 9

To generate the SVG in Listing 9, the JavaScript code in Listing 10 combines D3's pack layout with some of my own graphical calculations:

Listing 10. JavaScript that combines pack layout with more graphical calculations
<!DOCTYPE html>

<meta charset="utf-8">
<body>
<!--
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>
 -->
<script src="d3.v3.js"></script>
<script>

var views = [
  [7057, 7483, 3749, 3846, 4598],
  [ 2371, 7397, 4589, 2861, 8249],
  [ 5972, 5672, 9152, 9725, 8983],
		];


var colors = [
  "white",
  "blue",
  "gold",
  "green", 
  "red", 
  "maroon"   ];

var width = 1500, height = 1000;
var widthOfOnePack = 300, heightOfOnePack = 300;
var spaceBetweenPacks = 20;
var packCalculations = [];
for (var count=0; count<3; count++){
  var pack = d3.layout.pack()
              .size([widthOfOnePack, heightOfOnePack])
              .value(function(d) { 
                 return d.popularity; });
  packCalculations[count] = 
     pack.nodes(
       getJSONForOneWeekOfPopularityData(count)
               );
}
var svg = d3.select("body").append("svg")
   .attr("width", width)
   .attr("height", height);
var g1 = svg.selectAll("g")
          .data(packCalculations).enter().append("g")
     .attr("transform", function(d,i){
             return "translate(" + 
            (widthOfOnePack + spaceBetweenPacks )*i + 
            "," + height / 7 + ")" });
g1.append("text")
.text(function(d, i){return "Week " + (i+1)})
  .attr("x", 60);

var g2 = g1.selectAll("g")
   .data(function(d){return d;})
   .enter()
   .append("g");

g2.append("circle")
  .attr("r", function(d, i){return d.r})
  .style("fill", function(d, i){return colors[i%6];})
  .attr("transform", function(d,i){
         return "translate(" + d.x + "," + d.y + ")"});

g2.append("text")
  .attr("x", -10)
  .attr("fill", "grey")
  .text(function(d, i){return d.value})
  .attr("transform", function(d,i){
        return "translate(" + d.x + "," + d.y + ")"});

function getJSONForOneWeekOfPopularityData(week){
var viewsJSONString = 
  "{ \"name\": \"week" + week + "\", \"children\": [" ;
  for (var count=0;
           count<5;
           count++   ){
  viewsJSONString += 
    "{ \"name\": \"" + colors[count+1] + " \", \"popularity\": " 
    + views[week][count] + " }   ";

  if (count<4) viewsJSONString += ",";

  }
  viewsJSONString += "]}"
  return JSON.parse(viewsJSONString);
}

</script>
</body>

I highlighted the packCalculations and transform enhancements in Listing 10 to help you compare this code with Listing 2.

In Listing 2 I declare packCalculations as a simple variable, whereas in Listing 10 I declare it as an array. That's because Listing 10 handles three packs of circles, and each pack has its own graphical calculations.

After I declare the packCalculations array, I run a simple loop in which I use D3's pack layout to perform the calculations for positioning each circle within the pack. D3 returns an array of nodes for each week. I store each set of D3's calculations in the packCalculations array. So packCalculations now is an array of an array of nodes.

Note an interesting point here. All of D3's calculations are performed for one pack, independent of other packs, as if only one pack were to be handled. These calculations don't consider that other packs might be on the same SVG canvas. But when the transform attribute translates the position of a week-wrapper tag, everything inside the week-wrapper is positioned relative to the wrapper — so you can easily position multiple packs of circles.

Now look at the calculation, which is shown in bold in Listing 10, of the week-wrapper tags' transform attribute values. I multiply the width of one pack plus a bit of white space by the index i. This adjustment places the first week-wrapper (i=0) without any horizontal displacement.The second week wrapper (i=1) is horizontally displaced by the width of the first week-wrapper plus a small amount of white space. Similarly, the third week-wrapper is displaced by the width of the first two week-wrappers and the additional white space.

The rest of Listing 10 is the same as Listing 2, where you draw individual circles of each pack. Clearly, you can:

  • Play with layouts by designing a parent-child arrangement of <g> tags.
  • Use each <g> tag's transform attribute values to place individual components or complete layouts of components at desired locations.

A pack layout of circles within circles

Now I'll show you some interesting enhancements that you can make to Listing 10 to add user-interaction data within the layout of Figure 3, thereby forming the pack of circles-within-circles that's shown in Figure 4.

Listing 11 shows the JavaScript for drawing Figure 4:

Listing 11. JavaScript to draw Figure 4
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!--
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>
 -->
<script src="d3.v3.js"></script>

<script>

var viewsAndInteraction = [
[ [7057, 2052], [7483, 2089], [3749, 1586], [3846, 1426], [4598, 2632] ],
[ [5972, 2071], [5672, 2190], [9152, 7214], [9725, 3782], [8983, 2721] ],
[ [8749, 3076], [4768, 3190], [6738, 4532], [9546, 3825], [6983, 4831] ]
               ];

var viewColors = [ "white", "blue", "gold", "green", "red", "maroon" ];
var interactionColors = [ "white", "lightblue", "yellow", "lightgreen",
   "lightcoral", "indianred" ];
var width = 1500, height = 1000;
var widthOfOnePack = 300, heightOfOnePack = 300;
var spaceBetweenPacks = 20;
var packCalculations = [];
  for (var count=0;
           count<3;
           count++   ){
    var pack = d3.layout.pack()
    .size([widthOfOnePack, heightOfOnePack])
    .value(function(d) { return d.size; });
    packCalculations[count] = pack.nodes(
    getJSONForOneWeekOfPopularityAndInteractionData(
                  count));
}
var svg = d3.select("body").append("svg")
   .attr("width", width)
   .attr("height", height);
var g1 = svg.selectAll("g")
         .data(packCalculations).enter().append("g")
         .attr("transform", function(d,i){
         return "translate(" + 
           (widthOfOnePack + spaceBetweenPacks)*i + "," + 
              height / 7 + ")" }
         );
g1.append("text")
.text(function(d, i){return "Week " + (i+1)})
  .attr("x", 60);
var g2 = g1.selectAll("g")
   .data(function(d){return d;})
   .enter()
   .append("g");

g2.append("circle")
  .attr("r", function(d, i){return d.r})
  .style("fill", function(d, i){return viewColors[i%6];})
  .attr("transform", function(d,i){
         return "translate(" + d.x + "," + d.y + ")"});

g2.append("circle")
  .attr("r", function(d, i){
        return ((d.interaction*d.r)/d.value);})
  .style("fill", function(d, i){return interactionColors[i%6];})
  .attr("transform", function(d,i){
         return "translate(" + d.x + "," + d.y + ")"});

g2.append("text")
  .attr("x", -15)
  .attr("y", 35)
  .attr("fill", "white")

  .text(function(d, i){return d.value})
  .attr("transform", function(d,i){
       return "translate(" + d.x + "," + d.y + ")"});

g2.append("text")
  .attr("x", -15)
  .attr("y", 5)

  //.attr("fill", "grey")
  .text(function(d, i){return d.interaction})
  .attr("transform", function(d,i){
       return "translate(" + d.x + "," + d.y + ")"});

function getJSONForOneWeekOfPopularityAndInteractionData(week){
var viewsAndInteractionJSON = 
  "{ \"name\": \"week" + week + "\", \"children\": [" ;
  for (var count=0; count<5; count++){
    viewsAndInteractionJSON += 
     "{ \"name\": \"" + viewColors[count+1] + 
      " \", \"size\": \"" + viewsAndInteraction[week][count][0] +       
      "\", \"interaction\": \"" + 
      viewsAndInteraction[week][count][1] + "\"     }   ";
  if (count<4) viewsAndInteractionJSON += ",";
  }
  viewsAndInteractionJSON += "]}";
  return JSON.parse(viewsAndInteractionJSON) ;
}

</script>
</body>

The enhancements (shown in bold) in Listing 11 are:

  • The array-to-JSON conversion logic is more complex, because this time you must generate the JSON of Listing 5, in which each leaf node has two pieces of data: popularity and user interaction.
  • The value() function of the pack layout object still works using the d.popularity attribute, ignoring the user-interaction attribute altogether. The graphical calculations for the pack layout are still based on popularity data. User-interaction data comes into the picture only to draw an inner circle within the outer popularity circle. Notice the authoring of the r attribute value (radius) of the inner user-interaction circle, which is shown in bold in Listing 11. That's where user-interaction data comes into play. To calculate the inner circle's radius, I multiply the user-interaction data by the ratio of the outer (popularity) circle's radius and the actual popularity data. This small graphical manipulation scales the inner circle proportionately to the scale that D3 uses to do its own internal calculations.

Bar chart representation of popularity and user-interaction data

I discussed representing social data by using circles in different layouts. Now I add a bit of variety. Figure 5, for example, shows bar charts in addition to circles to represent the same social data:

Figure 5. Bar chart representation of social data
Bar chart representation of social data

You can see that the bar charts in Figure 5 also represent the same three weeks of popularity and user-interaction data. The bars use the same colors and shades that I use to draw the inner and outer circles. The solid portion of each bar represents popularity, and more lightly shaded portion represents user interaction.

Listing 12 shows the SVG code that draws the bar charts in Figure 5 (omitting the code for the circles):

Listing 12. SVG for bar charts
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="1500" height="1000">

<g>
<!--Tags for circles omitted.-->
</g>

<g>

<g transform="translate(100,250)">

  <g transform="translate(0,200)">
    <rect height="141.14" width="20" fill="blue"></rect>
    <rect height="41.04" width="20" fill="lightblue"></rect>
  </g>

  <g transform="translate(22,200)">
    <rect height="149.66" width="20" fill="gold"></rect>
    <rect height="41.78" width="20" fill="yellow"></rect>
  </g>

  <g transform="translate(44,200)">
    <rect height="74.98" width="20" fill="green"></rect>
    <rect height="31.72" width="20" fill="lightgreen"></rect>
  </g>

  <g transform="translate(66,200)">
    <rect height="76.92" width="20" fill="red"></rect>
    <rect height="28.52" width="20" fill="lightcoral"></rect>
  </g>

  <g transform="translate(88,200)">
    <rect height="91.96" width="20" fill="maroon"></rect>
    <rect height="52.64" width="20" fill="indianred"></rect>
  </g>
</g>

<g transform="translate(420,250)">

  <g transform="translate(0,200)">
    <rect height="119.44" width="20" fill="blue"></rect>
    <rect height="41.42" width="20" fill="lightblue"></rect>
  </g>

  <g transform="translate(22,200)">
    <rect height="113.44" width="20" fill="gold"></rect>
    <rect height="43.8" width="20" fill="yellow"></rect>
  </g>

  <g transform="translate(44,200)">
    <rect height="183.04" width="20" fill="green"></rect>
    <rect height="144.28" width="20" fill="lightgreen"></rect>
  </g>

  <g transform="translate(66,200)">
    <rect height="194.5" width="20" fill="red"></rect>
    <rect height="75.64" width="20" fill="lightcoral"></rect>
  </g>

  <g transform="translate(88,200)">
    <rect height="179.66" width="20" fill="maroon"></rect>
   <rect height="54.42" width="20" fill="indianred"></rect>
  </g>
</g>

<g transform="translate(740,250)">

  <g transform="translate(0,200)">
    <rect height="174.98" width="20" fill="blue"></rect>
    <rect height="61.52" width="20" fill="lightblue"></rect>
  </g>

  <g transform="translate(22,200)">
    <rect height="95.36" width="20" fill="gold"></rect>
    <rect height="63.8" width="20" fill="yellow"></rect>
  </g>

  <g transform="translate(44,200)">
    <rect height="134.76" width="20" fill="green"></rect>
    <rect height="90.64" width="20" fill="lightgreen"></rect>
  </g>

  <g transform="translate(66,200)">
    <rect height="190.92" width="20" fill="red"></rect>
    <rect height="76.5" width="20" fill="lightcoral"></rect>
  </g>

  <g transform="translate(88,200)">
    <rect height="139.66" width="20" fill="maroon"></rect>
    <rect height="96.62" width="20" fill="indianred"></rect>
  </g>
</g>

</g>
</svg>

You can see the familiar arrangement of <g> tags in Listing 12. Three <g> tags each wrap a week of data. Then, each of the week-wrapper tags has five child <g> tags, each of which in turn wraps a pair of <rect> tags to represent the actual popularity and interaction data. A <rect> tag draws a rectangular bar, just as a <circle> tag draws a circle.

Each <rect> tag has width and height attributes. The width attribute value is fixed for all <rect> tags, and the height attribute value is proportional to the social data that the rectangular bar represents. The fixed width and variable height of the bars give the impression of a bar chart.

Once again, the <g> tags' transform attributes place each of the bars at their correct positions. In other words, there's no fundamental difference between the SVG code for circles and bars.

The D3-based JavaScript code for generating the bar charts in Figure 5 is included in this article's source code download.

Adding navigation data

Often you need to display various graphical components on one page, so I'll finish by putting everything together on one SVG canvas. Figure 6 adds navigation data for three weeks using the chord diagram that you saw in Part 1:

Figure 6. Circles, arcs, chords, layouts, and bar charts in a single drawing
Circles, arcs, chords, layouts, and bar charts in a single drawing

The JavaScript for Figure 6 is also included in the source code download.


Wrap-up

You've made a long data-visualization journey — from the humble circles of Part 1's Figure 1 to the sophisticated arrangements in this article — in the short space of this series. I hope these articles whet your appetite to learn about D3's other features, which you can discover by exploring the D3 website.

One of D3's great benefits is that it's based on JavaScript, the preferred front-end technology for top business applications. If you use a business-application platform that supports JavaScript — such as IBM Business Process Manager — you can build graphical features from this series of articles right into your business applications. To learn about the IBM Business Process Manager and its support for external JSON and JavaScript libraries, see Resources for a link to the IBM Business Process Manager Information Center.


Download

DescriptionNameSize
Sample codesource-code.zip10KB

Resources

Learn

Get products and technologies

  • D3: Download the D3 JavaScript library.
  • IBM InfoSphere Streams: Download InfoSphere Streams and build applications that rapidly ingest, analyze, and correlate information as it arrives from thousands of real-time sources.
  • IBM InfoSphere BigInsights: Download InfoSphere BigInsights and manage and analyze massive volumes of structured and unstructured data at rest.

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users while you explore the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Big data and analytics, Web development
ArticleID=937232
ArticleTitle=Data visualization, Part 2: Use D3 component layouts
publish-date=07152013