• No results found

Pie Chart Layout

In document Practical D3.Js (Page 125-130)

Pie and donut charts are probably the most hated charts within the data visualization community. In his book, “The Visual Display of Quantitative Information,” Edward Tufte wrote that “a table is nearly always better than a dumb pie chart.” Problem is that they are overused. Nevertheless, you most probably need to use pie and donut charts sometimes.

As mentioned in the previous chapter, you can use arc generators to create pie and donut charts.

However, using arc generators only, without the use of layouts, makes the creation of these two charts a boring and a non scalable process. Paul Graham of Y Combinator advised startups to “do things that don’t scale,” and once their business model is sound, they shall find ways to automate stuff and make them scalable. I’d like to tweak his advice here and tell you to try creating your charts the most boring and least flexible way first, and once you learn how things work, use more advance techniques. We, thus, are going create the most basic donut chart now. It is divided into two equal sectors, each in a different color, as shown in Figure  7-1 .

110

To draw the path shown in Figure  7-1 , you have to use the arc generator. It has four main methods: outerRadius() , innerRadius() , startAngle() , and endAngle() . The outerRadius() specifies the size of the circle, while the innerRadius() specify size of the hole you want to dig into that circle. The innerRadius() here is what differentiate between pie and donut charts; pie charts have an inner radius of zero. Figure  7-1 has two sectors, each has its own startAngle and endAngle. Here is the code for the chart in Figure  7-1 :

var w= 200, h = 200;

var svg = d3.select('body').append('svg') .attr('width', w)

.attr('height', h);

var colors = d3.scale.category10(); var arc = d3.svg.arc()

.innerRadius(30) .outerRadius(80)

.startAngle(function(d){ return d['start']; }) .endAngle(function(d){ return d['end']; }); svg.selectAll("path")

.data([

{'start': 0, 'end': Math.PI}, {'start': Math.PI, 'end': 2*Math.PI} ]) .enter() .append("path") .attr("d,” arc) .attr("fill,” function(d,i){ return colors(i); }) .attr("stroke,” "#FFFFFF") .attr("stroke-width,” "3px") .attr("transform,” "translate(" + w/2 + ,”" + h/2 + ")");

As usual, we started by setting the drawing area, called svg, and then we set its width and height to 200 pixels. One new scale that we are seeing for the first time here is d3.scale.category10(), this will to give us a nice set of colors, that we can use later on. As you can tell from its name, it has 10 colors, #1f77b4, #ff7f0e, #2ca02c, and so on. So, now if you enter colors(0) in the console, it will return #1f77b4. Obviously, colors(1) will return #ff7f0e, and so on. You can also try .category20(), .category20b() and .category20c().

The arc generator gives all the arcs, or sectors, an inner radius of 30 and and outer radius of 80. Because each arc has different start and end angles, we set these values from the data, which is an array of object, each has its start and end attributes. D3 uses Radian angles, which means that a complete circle goes from 0 to 2π, rather than 0 to 360 degrees. Surely you are an expert in setting paths attributes and styling them, so we won’t bore you by explaining this part. The very last part of the code transforms the final shape and moves it to the centre of the drawing area, or else the “donut” would have had its centre in the upper left corner of our drawing area, and consequently, most of it would have been invisible. Try removing the transformation line to see what we mean.

Now, have one more look at the data used in this pie chart; we specify start and end angles within each element. Let’s say, we want to draw another chart to show what percentages of people go to their work by car versus bus and those who like to walk. Our data might then be as follows:

CHAPTER 7 ■ TRANSFORMING DATA WITH LAYOUTS

111

var transportation = [

{ 'mean': 'Car' , 'value': 30 }, { 'mean': 'Bus' , 'value': 60 }, { 'mean': 'Walk', 'value': 10 } ];

The car sector should be represented by an arc whose area is 30 percent of the circle. In other words, set its startAngle to zero and its endAngle 0.3 * 2π. Setting the bus sector is a little bit more challenging. Its start angle should start where the previous sector ends. So, its startAngle will be 0.3 * 2π, and its end angle will be 0.3 * 2π + 0.6 * 2π. And for the walk sector, the startAngle should be 0.3 * 2π + 0.6 * 2π, and the endAngle will be 0.3 * 2π + 0.6 * 2π + 0.1 * 2π, or just 2π.

As you can see, we have two issues here. The easy issue is how can we map the 30 to 0.3 * 2π, and map 60 to 0.6 * 2π, and so on. This is the easy issue; we can just use a scale like this one:

var pieScale = d3.scale.linear().domain([0, 100]).range([0, 2 * Math.PI]);

The second issue is the hard one. The start and end angles for each sector of the chart, do not only depend on their corresponding element, but they also depend on the previous elements in the array. Is there a solution for this issue, then? Yes, and it is called layouts. In fact, the pie layout solves both issues for us.

In the previous chapter, we have seen path generators that help us convert data items into path strings. Layouts on the other hand, as explained by the D3 manual, “typically operate across a collection of data as a whole, rather than individually.” They see the data as a whole, and converts individual items while keeping an eye on the other items in the data array.

A very basic pie layout, is defined as follows: myPie = d3.layout.pie();

As you have seen in the previous chapters with scales and axis, what pie layout returns is a function that you can apply to your data. Now, if you call myPie() and give it an array of numbers, it will return an array of objects each with its corresponding startAngle(), endAngle(), and some other attributes such as the original data you passed to it. After defining your pie layout, you can now try writing myPie([5,3,2]), myPie([4,2,3,1]) or myPie([3,3]) in the console of your browser’s developers tools and see what you will get:

myPie([3,3]);

// Gives you the following output // [ // { // data: 3, // endAngle: 3.141592653589793, // startAngle: 0, // value: 3, // }, { // data: 3, // endAngle: 6.283185307179586, // startAngle: 3.141592653589793, // value: 3, // } // ]

As you can see, the layout inspects the the data as a whole, and decides what are the start and end angles for each item accordingly.

112

Obviously, your input data can be more complex than an array of numbers. A value() method gets you covered on this, where you can define how the layout extracts values form your data. Don’t want your chart to be in the form of a whole circle, you can use its startAngle() and endAngle() methods to define the span of your chart. Additionally, you can sort your data on the fly using sort(), and have some spacing between your sectors using padAngle(). Notice that padAngle() is only supported in versions 3.5 and up.

Now, you are all set to draw the following chart within the same drawing area, svg, and using the same transportation dataset and color scale we defined earlier.

As shown in Figure  7-2 , we decided to draw a semicircle this time—the start and end angles for the layout span between -0.5 π and 0.5 π. Here is the code needed to draw the chart:

// Define the pie layout. var pie = d3.layout.pie()

.startAngle(-0.5*Math.PI) .endAngle(0.5*Math.PI) .padAngle(0.04) .sort(null) .value(function(d){ return d.value; });

// Define an arc generator. var arc = d3.svg.arc() .innerRadius(40) .outerRadius(150) .startAngle(function(d){ return d.startAngle; }) .endAngle(function(d){ return d.endAngle; });

CHAPTER 7 ■ TRANSFORMING DATA WITH LAYOUTS

113

// Use the layout and generator in drawing the char. svg.selectAll("path") .data(pie(transportation)) .enter() .append("path") .attr("d," arc) .attr("fill," function(d,i){ return colors(i); }) .attr("transform," "translate("+w/2+,""+h/2+")");

The steps involved should be familiar by now: You start first by defining both the pie layout and the arc generator. The pie layout spans between -0.5 π and 0.5 π, it has a padding of 0.04 between the different sectors. You do not want to sort the data, thus sort is set to null. Finally, you tell it how to extract the values from the transportation dataset using the value function. The arc generator sets the inner and outer radii for all arcs to 40 and 150, respectively. Because the generator is going to deal with our data after it has been processed by the layout, therefore, each item in the dataset already have new fields added to it by the layout, including startAngle() and endAngle(). The startAngle() and endAngle() methods of the generator it able to use these new fields now. Notice in third part of the code, the defined layout is applied to the data before giving it to the data method of the path.

Nothing stops us from having varying inner and outer radii for each arc. Do you feel like being artistic today? You can tweak the generator a little bit as follows to get the result in Figure  7-3 :

var arc = d3.svg.arc()

.innerRadius(function(d,i){ return 30 + 20 * i;}) .outerRadius(function(d,i){ return 120 + 20 * i;}) .startAngle(function(d){ return d.startAngle; }) .endAngle(function(d){ return d.endAngle; });

114

In document Practical D3.Js (Page 125-130)