Sometime last year, I concluded two truly awesome (to me, at least) web tutorials on
Bar Charts and
Line Graphs using HTML, a lot of CSS, and a bit of JavaScript. Today, I bring you... the Pie Chart.
Pie Charts are primarily used to show relative amounts, or percentages. And they're useful for determining, at a glance, what the majority statistic is. Bar Charts can do that too - they're versatile that way - but not as well as a Pie Chart. There's going to be a fair amount of math involved in this one. You'll need to understand percentages and geometry, at the very least.
For the purposes of this web tutorial, we will be using the same dataset that was employed during the last two web tutorials. We'll also be reusing some functions and CSS classes.
Without further ado...
Here's the beginning HTML. You'll see that I've included, in the interest of saving time, some of the HTML and CSS we used during the
Bar Chart and
Line Graph web tutorials. The
graph_container div is there, as well as
legend_container div, and the two dropdown lists,
ddlRow and
ddlStat.
For the JavaScript, I've included the dataset
graphdata, as well as the functions
populate(),
getMaxStatistic(),
displayData() and
displayLegend(). There will be changes to some of the stuff, but as with the previous web tutorials,
populate() will call
displayData() and
displayLegend() at the end.
displayLegend() and
getMaxStatistic() are unchanged from the last tutorial.
The page loads
populate() and the dropdown lists trigger
displayData(), as per the last web tutorials.
For the CSS, I've left the styling for the
graph_container largely intact (except it's now a 400 pixel square and centered), and the styling for the legend is identical to the
Line Graph web tutorial's.
<!DOCTYPE html>
<html>
<head>
<title>Pie Chart</title>
<style>
#graph_container
{
height: 400px;
width: 400px;
margin: 0 auto 0 auto;
}
#legend_container
{
color: #FFFFFF;
font-weight: bold;
font-family: verdana;
font-size: 1em;
width: 50%;
margin: 5% auto 0 auto;
padding: 0.5em;
}
.legend_row
{
width: 100%;
height: 1.5em;
}
#legend_row: after
{
display: block;
content: "";
clear: both;
}
.legend_color
{
width: 1em;
height: 1em;
float: left;
}
.legend_label
{
height: 1em;
float: left;
color: #000000;
font-weight: bold;
font-family: verdana;
font-size: 1em;
margin-left: 1em;
}
</style>
<script>
var graphdata =
{
"cols":
[
{
"title": "Fernando Torres",
"color": "#FF00FF",
"stats":
[
{"year": 2007, "goals": 24, "appearances": 33},
{"year": 2008, "goals": 14, "appearances": 24},
{"year": 2009, "goals": 18, "appearances": 22},
{"year": 2010, "goals": 9, "appearances": 23},
]
},
{
"title": "Steven Gerrard",
"color": "#440000",
"stats":
[
{"year": 2007, "goals": 11, "appearances": 36},
{"year": 2008, "goals": 16, "appearances": 34},
{"year": 2009, "goals": 9, "appearances": 31},
{"year": 2010, "goals": 4, "appearances": 33},
]
},
{
"title": "Dirk Kuyt",
"color": "#FFFF00",
"stats":
[
{"year": 2007, "goals": 3, "appearances": 34},
{"year": 2008, "goals": 12, "appearances": 32},
{"year": 2009, "goals": 9, "appearances": 38},
{"year": 2010, "goals": 13, "appearances": 37},
]
},
{
"title": "Ryan Babel",
"color": "#00AA00",
"stats":
[
{"year": 2007, "goals": 4, "appearances": 30},
{"year": 2008, "goals": 3, "appearances": 27},
{"year": 2009, "goals": 4, "appearances": 25},
{"year": 2010, "goals": 1, "appearances": 1},
]
},
{
"title": "Yossi Benayoun",
"color": "#000044",
"stats":
[
{"year": 2007, "goals": 4, "appearances": 30},
{"year": 2008, "goals": 8, "appearances": 32},
{"year": 2009, "goals": 6, "appearances": 30},
]
},
{
"title": "David N'gog",
"color": "#006699",
"stats":
[
{"year": 2008, "goals": 2, "appearances": 14},
{"year": 2009, "goals": 5, "appearances": 24},
{"year": 2010, "goals": 2, "appearances": 25},
]
},
],
"rows": [2007, 2008, 2009, 2010],
"stats": ["goals", "appearances"]
};
function populate()
{
displayLegend();
displayData();
}
function displayData()
{
}
function getMaxStatistic(haystack, needle)
{
for (var i = 0; i < haystack.length; i++)
{
if (haystack[i].stats < haystack[needle].stats)
{
return false;
}
}
return true;
}
function displayLegend()
{
var legend = document.getElementById("legend_container");
legend.innerHTML = "";
var color, label;
var row;
for (var i = 0; i < graphdata.cols.length; i++)
{
color = document.createElement("div");
color.className = "legend_color";
color.style.backgroundColor = graphdata.cols[i].color;
label = document.createElement("div");
label.className = "legend_label";
label.innerHTML = graphdata.cols[i].title;
row = document.createElement("div");
row.className = "legend_row";
row.appendChild(color);
row.appendChild(label);
legend.appendChild(row);
}
}
</script>
</head>
<body onload="populate();">
<div id="graph_container">
</div>
<div id="legend_container">
</div>
<select id="ddlRow" onchange="displayData();">
</select>
<select id="ddlStat" onchange="displayData();">
</select>
</body>
</html>
So let's do this now. Set all divs to have a
red outline.
div {outline: 1px solid #FFAA00;}
#graph_container
{
height: 400px;
width: 400px;
margin: 0 auto 0 auto;
}
For the sake of visibility, this is what you have now.
Let's start with some smaller stuff. Populate the drop-down lists as shown. If you've been following the previous web tutorials, this should be clear; I'd rather not repeat myself.
function populate()
{
var ddl,option;
ddl = document.getElementById("ddlRow");
for (var i=0;i<graphdata.rows.length;i++)
{
option = document.createElement("option");
option.text = graphdata.rows[i] + " - " + (parseInt(graphdata.rows[i])+1);
option.value = graphdata.rows[i];
ddl.add(option);
}
ddl = document.getElementById("ddlStat");
for (var i=0;i<graphdata.stats.length;i++)
{
option = document.createElement("option");
option.text = graphdata.stats[i];
option.value = graphdata.stats[i];
ddl.add(option);
}
displayLegend();
displayData();
}
Your drop-down lists! Right now, they don't do anything (other than fire off the empty displayData() function), but that's fine.
OK, down to (more) serious business! Within your
graph_container div, you should have a div for your data labels, id
label_wrapper, followed by a div for your pie, id
pie_container.
<div id="graph_container">
<div id="label_wrapper">
</div>
<div id="pie_container">
</div>
</div>
Here's the styling. Both of these divs will occupy the
graph_container div fully (100% height and width) and floated left.
pie_container will be circular, so the
border-radius property is at 50%, and
overflow is set to
hidden.
label_wrapper will overlap
pie_container, so the
margin-right property is set to negative 100% while a
display and
z-index property needs to be specified. You won't see any change in your browser after refreshing it. It's a perfect overlap.
div {outline:0px solid #FFAA00;}
#graph_container
{
height: 400px;
width: 400px;
margin: 0 auto 0 auto;
}
#pie_container
{
height: 100%;
width: 100%;
border-radius: 50%;
float: left;
overflow: hidden;
}
#label_wrapper
{
height: 100%;
width: 100%;
margin-right: -100%;
float: left;
position: relative;
z-index: 2000;
}
Now we're gonna add another div inside label_wrapper. This will have an id of
label_quad_wrapper.
<div id="graph_container">
<div id="label_wrapper">
<div id="label_quad_wrapper">
</div>
</div>
<div id="pie_container">
</div>
</div>
It'll be styled this way. It's going to occupy the top right quarter of the
label_wrapper div.
#label_wrapper
{
height: 100%;
width: 100%;
margin-right: -100%;
float: left;
position: relative;
z-index: 2000;
}
#label_quad_wrapper
{
height: 50%;
width: 50%;
margin-left: 50%;
}
#legend_container
{
color: #FFFFFF;
font-weight: bold;
font-family: verdana;
font-size: 1em;
width: 50%;
margin: 5% auto 0 auto;
padding: 0.5em;
}
Next, we add two divs in the
pie_container div. One will be
quad_wrapper_left and the other will be
quad_wrapper_right. The ids are important! They will both have the style
quad_wrapper.
<div id="graph_container">
<div id="label_wrapper">
<div id="label_quad_wrapper">
</div>
</div>
<div id="pie_container">
<div id="quad_wrapper_left" class="quad_wrapper">
</div>
<div id="quad_wrapper_right" class="quad_wrapper">
</div>
</div>
</div>
This is how we'll style these. They will take up both halves of the pie_container div.
#pie_container
{
height: 100%;
width: 100%;
border-radius: 50%;
float: left;
overflow: hidden;
}
.quad_wrapper
{
width: 50%;
height: 100%;
float: left;
}
#label_wrapper
{
height: 100%;
width: 100%;
margin-right: -100%;
float: left;
position: relative;
z-index: 2000;
}
See that?
pie_container has two equal-sized divs, one taking the left side, and one taking the right. And because it's overlapped with
label_wrapper, the
label_quad_wrapper div can still be seen on the top right corner!
Now for the hard part...
We'll be working with the data, to put in the labels.
First, let's define a class for those labels.
The class
label_quad will be used to store those pie slices we'll be putting in
label_quad_wrapper. The pie slices will be the full size of
label_quad_wrapper, floated left, and they'll be rotated by the bottom left corner.
data_label is another class. I'm setting the text to
black for now. But the other properties are mostly cosmetic. I set the
position property explicitly to
relative for these two classes so there's no misunderstanding on the part of the browser.
#label_quad_wrapper
{
height: 50%;
width: 50%;
margin-left: 50%;
}
.label_quad
{
width: 100%;
height: 100%;
margin-bottom: -100%;
-webkit-transform-origin: 0% 100%;
transform-origin: 0% 100%;
position: relative;
}
.data_label
{
color: #000000;
font-weight: bold;
font-family: verdana;
font-size: 0.8em;
position: relative;
}
#legend_container
{
color: #FFFFFF;
font-weight: bold;
font-family: verdana;
font-size: 1em;
width: 50%;
margin: 5% auto 0 auto;
padding: 0.5em;
}
Now that we've defined those classes, let's get to work with the JavaScript. Pay close attention, because there'll be quite a few moving parts in this one. We're just going to place the data labels first.
Here, we start with the
displayData() function. Get the current values of the
ddlStat and
ddlRow drop-down lists and assign them to the variables
stat and
row, respectively.
function displayData()
{
var stat = document.getElementById("ddlStat").value;
var row = document.getElementById("ddlRow").value;
}
Create the variable
nonzero and set it to the value of a function,
getNonZero(), with
row and
stat passed in as arguments. Then create the function
getNonZero. It'll take in two parameters - you guessed it -
row and
stat. The purpose of this function is to return
only values that are non-zero... because showing data that is zero in a pie chart just doesn't make much sense, does it?
function displayData()
{
var stat = document.getElementById("ddlStat").value;
var row = document.getElementById("ddlRow").value;
var nonzero = getNonZero(row, stat);
}
function getNonZero(row, stat)
{
}
First, define an array,
nonzero. Then variables
temp and
player. At the end of the function, you will return the array
nonzero.
function getNonZero(row, stat)
{
var nonzero = [];
var temp;
var player;
return nonzero;
}
Now, let's iterate through the cols array of the
graphdata object. In the
For loop, get the
stats array filtered. We only want those whose
year property match the
row variable. Set the resultant array to the variable
temp.
function getNonZero(row, stat)
{
var nonzero = [];
var temp;
var player;
for (var i = 0; i < graphdata.cols.length; i++)
{
temp = graphdata.cols[i].stats.filter(function (x) {return x.year == row;});
}
return nonzero;
}
Now, we only want to do stuff if the
temp array is not empty. There should be only one item in the array, so reference
temp[0], and grab the property corresponding with
stat. This being a JSON object, an array is really another type of object, so we can reference the
temp[0] object's properties by treating it as a two-dimensional array! Again, we only want to do stuff if the stat in question is greater than zero.
function getNonZero(row, stat)
{
var nonzero = [];
var temp;
var player;
for (var i = 0; i < graphdata.cols.length; i++)
{
temp = graphdata.cols[i].stats.filter(function (x) {return x.year == row;});
if (temp.length > 0)
{
if (temp[0][stat] > 0)
{
}
}
}
return nonzero;
}
If there's non-zero data, set
player to a newly created object and assign it two properties -
color and
stats. Grab the appropriate data from the
graphdata and
temp[0] object, then add the resultant object to the
nonzero array. Basically, this function gives you an array of objects corresponding to the year and statistic selected in the drop-down lists, and their corresponding color and value in the
graphdata object! And makes sure the values selected are non-zero, of course.
function getNonZero(row, stat)
{
var nonzero = [];
var temp;
var player;
for (var i = 0; i < graphdata.cols.length; i++)
{
temp = graphdata.cols[i].stats.filter(function (x) {return x.year == row;});
if (temp.length > 0)
{
if (temp[0][stat] > 0)
{
player =
{
"color": graphdata.cols[i].color,
"stats": temp[0][stat]
};
nonzero.push(player);
}
}
}
return nonzero;
}
For instance, if you passed "2009" and "goals" into the
getNonZero() function, you would get an array of objects like this.
[
{
"color": "#FF00FF",
"stats": 18
},
{
"color": "#440000",
"stats": 9
},
{
"color": "#FFFF00",
"stats": 9
},
{
"color": "#00AA00",
"stats": 4
},
{
"color": "#000044",
"stats": 6
},
{
"color": "#006699",
"stats": 5
}
]
So back to the
displayData() function. After obtaining the array of non-zero statistics, run it through the
getSorted() function and assign the result to the
sorted variable. And then create the
getSorted() function.
function displayData()
{
var stat = document.getElementById("ddlStat").value;
var row = document.getElementById("ddlRow").value;
var nonzero = getNonZero(row, stat);
var sorted = getSorted(nonzero);
}
function getNonZero(row, stat)
{
var nonzero = [];
var temp;
var player;
for (var i = 0; i < graphdata.cols.length; i++)
{
temp = graphdata.cols[i].stats.filter(function (x) {return x.year == row;});
if (temp.length > 0)
{
if (temp[0][stat] > 0)
{
player =
{
"title": graphdata.cols[i].title,
"color": graphdata.cols[i].color,
"stats": temp[0][stat]
};
nonzero.push(player);
}
}
}
return nonzero;
}
function getSorted(nonzero)
{
}
We first define
sorted as an empty array, and
temp as a variable, assigning the value of
nonzero to it. At the end of the function, we return
sorted. This function basically accepts the array of non-zero values,
nonzero, and sorts them from largest to smallest. There's a reason why we need this. It will all be clear later.
function getSorted(nonzero)
{
var sorted = [];
var temp = nonzero;
return sorted;
}
Now we'll be running the next segment of code for as long as
temp is not an empty array. If
temp has only one element, we add that element to
sorted, then remove it from
temp using the
splice() method. That would make
temp an empty array, which is when the loop ends.
function getSorted(nonzero)
{
var sorted = [];
var temp = nonzero;
while (temp.length > 0)
{
if (temp.length == 1)
{
sorted.push(temp[0]);
temp.splice(0, 1);
}
else
{
}
}
return sorted;
}
But if
temp has more than one element, we iterate through
temp. And run the
getMaxStatistic() function, passing in
temp and the current pointer.
function getSorted(nonzero)
{
var sorted = [];
var temp = nonzero;
while (temp.length > 0)
{
if (temp.length == 1)
{
sorted.push(temp[0]);
temp.splice(0, 1);
}
else
{
for (var i = 0; i < temp.length; i++)
{
if (getMaxStatistic(temp, i))
{
}
}
}
}
return sorted;
}
If the result is
true, push the current element in the
temp array into
sorted, then remove that element from
temp. We already have the
getMaxStatistic() function. It will return
true if the element in the
temp array pointed to by
i, has the highest stat in the array.
So what this does, is probably not all that efficient... but it works and that's all we really need from it right now.
function getSorted(nonzero)
{
var sorted = [];
var temp = nonzero;
while (temp.length > 0)
{
if (temp.length == 1)
{
sorted.push(temp[0]);
temp.splice(0, 1);
}
else
{
for (var i = 0; i < temp.length; i++)
{
if (getMaxStatistic(temp, i))
{
sorted.push(temp[i]);
temp.splice(i, 1);
}
}
}
}
return sorted;
}
After using the
getSorted() function, the returned data should look like this.
[
{
"color": "#FF00FF",
"stats": 18
},
{
"color": "#440000",
"stats": 9
},
{
"color": "#FFFF00",
"stats": 9
},
{
"color": "#000044",
"stats": 6
},
{
"color": "#006699",
"stats": 5
},
{
"color": "#00AA00",
"stats": 4
},
]
Got all that? Great? We're going back to the
displayData() function now...
Now that we have sorted which is the array holding the sorted statistics from largest to smallest, we pass the array into the
getPieces() function as an argument and set the variable
pieces to the result. This function will derive all the pieces required to hold the data. We'll get to that in a moment.
function displayData()
{
var stat = document.getElementById("ddlStat").value;
var row = document.getElementById("ddlRow").value;
var nonzero = getNonZero(row, stat);
var sorted = getSorted(nonzero);
var pieces = getPieces(sorted);
}
In the
getPieces() function, we first define a variable,
total, intitialized to 0. Then an empty array,
pieces. Then we have another two variables,
currentPiece and
currentTotalPiece.
currentTotalPiece is also initialized to 0.
pieces is returned at the end of the function.
function getSorted(nonzero)
{
var sorted = [];
var temp = nonzero;
while (temp.length > 0)
{
if (temp.length == 1)
{
sorted.push(temp[0]);
temp.splice(0, 1);
}
else
{
for (var i = 0; i < temp.length; i++)
{
if (getMaxStatistic(temp, i))
{
sorted.push(temp[i]);
temp.splice(i, 1);
}
}
}
}
return sorted;
}
function getPieces(sorted)
{
var total = 0;
var pieces = [];
var currentPiece;
var currentTotalPiece = 0;
return pieces;
}
function getMaxStatistic(haystack, needle)
{
for (var i = 0; i < haystack.length; i++)
{
if (haystack[i].stats < haystack[needle].stats)
{
return false;
}
}
return true;
}
Next, we will obtain
total by iterating through the array
sorted and tallying up all the stats.
function getPieces(sorted)
{
var total = 0;
var pieces = [];
var currentPiece;
var currentTotalPiece = 0;
for (var i = 0; i < sorted.length; i++)
{
total += sorted[i].stats;
}
return pieces;
}
Next, we iterate through the
sorted array in reverse order. Why? Because
sorted is sorted from largest stat to smallest. We want to process the largest stats first. And why is
that? Relax, it'll be clear soon.
Our aim here is to determine how many degrees, out of a possible 360 degrees (which, geometrically, is what a circle offers), that each stat will take.
Inside the loop, if
i is 0, that means you are at the first element of
sorted (and smallest stat), in which case the size of
currentPiece is 360 degrees minus
currentTotalPiece. eBar that in mind, for now.
If not, we derive
currentPiece by calculating the ratio, which is the current stat divided by
total. Then we multiple that by 360 to get the number of degrees the stat will take. Using the
toFixed() method with 0 as an argument, will ensure that the decimal places are trimmed off. However, we want to use
currentPiece in another calculation next, so we have to use the
parseInt() function.
currentTotalPiece is the number of degrees that have been used up by your pieces so far, so it will be incremented by
currentPiece. Of course, when you get to the final and smallest stat, we merely have to take 360 less
currentTotalPiece!
function getPieces(sorted)
{
var total = 0;
var pieces = [];
var currentPiece;
var currentTotalPiece = 0;
for (var i = 0; i < sorted.length; i++)
{
total += sorted[i].stats;
}
for (var i = sorted.length - 1; i >= 0; i--)
{
if (i == 0)
{
currentPiece = 360 - currentTotalPiece;
}
else
{
currentPiece = ((sorted[i].stats/total) * 360).toFixed(0);
currentTotalPiece += parseInt(currentPiece);
}
}
return pieces;
}
Of course, with each piece that is being processed, create an object with the
color,
stats, and the
piece property, which is set to the value of
currentPiece. Then added it to the
pieces array. Since we're using the
push() method and the
sorted array is being iterated through in reverse order, that means we'll get a sorted array with the largest piece, to the smallest piece!
function getPieces(sorted)
{
var total = 0;
var pieces = [];
var currentPiece;
var currentTotalPiece = 0;
for (var i = 0; i < sorted.length; i++)
{
total += sorted[i].stats;
}
for (var i = sorted.length - 1; i >= 0; i--)
{
if (i == 0)
{
currentPiece = 360 - currentTotalPiece;
}
else
{
currentPiece = ((sorted[i].stats / total) * 360).toFixed(0);
currentTotalPiece += parseInt(currentPiece);
}
pieces.push
(
{
"color": sorted[i].color,
"stats": sorted[i].stats,
"piece": currentPiece
}
);
}
return pieces;
}
The data derived from
getPieces() should be this...
[
{
"color": "#FF00FF",
"stats": 18,
"piece": 127
},
{
"color": "#440000",
"stats": 9,
"piece": 64
},
{
"color": "#FFFF00",
"stats": 9,
"piece": 64
},
{
"color": "#000044",
"stats": 6,
"piece": 42
},
{
"color": "#006699",
"stats": 5,
"piece": 35
},
{
"color": "#00AA00",
"stats": 4,
"piece": 28
}
]
Next thing we do is clear the
label_quad_wrapper div.
function displayData()
{
var stat = document.getElementById("ddlStat").value;
var row = document.getElementById("ddlRow").value;
var nonzero = getNonZero(row, stat);
var sorted = getSorted(nonzero);
var pieces = getPieces(sorted);
var label_quad_wrapper = document.getElementById("label_quad_wrapper");
label_quad_wrapper.innerHTML = "";
}
Now declare an object,
lastPieceAngle, and put in properties
prevangle and
newangle. Initialize both to 0. Set the value of
lastPieceAngle to the function
placeLabel(), using the first piece in
pieces and
lastPieceAngle as arguments. Then create the
placeLabel() function.
function displayData()
{
var stat = document.getElementById("ddlStat").value;
var row = document.getElementById("ddlRow").value;
var nonzero = getNonZero(row, stat);
var sorted = getSorted(nonzero);
var pieces = getPieces(sorted);
var lastPieceAngle = {"prevangle": 0, "newangle": 0};
var label_quad_wrapper = document.getElementById("label_quad_wrapper");
label_quad_wrapper.innerHTML = "";
lastPieceAngle = placeLabel(pieces[0], lastPieceAngle);
}
function placeLabel(piece, angle)
{
}
We'll get started with the
placeLabel() function now. Declare
newangle and
midangle as variables. Since each pie slice is supposed to begin and end at a particular angle, we derive
newangle from the
newangle variable, and the
newangle property of the
angle object becomes the previous angle,
prevangle.
As for
midangle, I'll explain in the next few paragraphs.
function placeLabel(piece, angle)
{
var newangle, midangle;
return {"prevangle" : angle.newangle, "newangle": newangle};
}
Here, we define the variable
label_quad as a newly created div element, and set its class to "label_quad". Remember, we created the CSS class earlier!
function placeLabel(piece, angle)
{
var newangle, midangle;
var label_quad = document.createElement("div");
label_quad.className = "label_quad";
return {"prevangle" : angle.newangle, "newangle": newangle};
}
Here, we append
label_quad as a child within
label_quad_wrapper. Then we create a span element and assign it to the variable
label. We give label a class of
data_label (which we've already defined in the CSS), set
label's text to the
stat property of the
piece object in the parameter, and append
label as a child within
label_quad!
function placeLabel(piece, angle)
{
var newangle, midangle;
var label_quad = document.createElement("div");
label_quad.className = "label_quad";
var label_quad_wrapper = document.getElementById("label_quad_wrapper");
label_quad_wrapper.appendChild(label_quad);
var label = document.createElement("span");
label.className = "data_label";
label.innerHTML = piece.stats;
label_quad.appendChild(label);
return {"prevangle" : angle.newangle, "newangle": newangle};
}
See that? You've just placed your first label. Change the value in the Seasons drop-down list to "2009 to 2010". The number should change to 18! Fernando Torres scored 18 goals in the 2009 to 2010 season, and naturally it's the highest stat of that season, so it comes first. However, we want it to appear in the middle of the slice. 18 out of 51 total goals (count all the goals scored in that season as a whole) is almost a third of the pie, so the number 18 should appear much further along.
Here,
newangle is set to the ending angle of the last piece, plus the size of the current piece.
function placeLabel(piece, angle)
{
var newangle, midangle;
var label_quad = document.createElement("div");
label_quad.className = "label_quad";
newangle = angle.newangle + parseInt(piece.piece);
var label_quad_wrapper = document.getElementById("label_quad_wrapper");
label_quad_wrapper.appendChild(label_quad);
var label = document.createElement("span");
label.className = "data_label";
label.innerHTML = piece.stats;
label_quad.appendChild(label);
return {"prevangle" : angle.newangle, "newangle": newangle};
}
midangle is the angle right in the middle of the current slice. We define
midangle as the size of the current slice,
piece.piece, divided by two, and added to the previous angle,
angle.newangle. Then we rotate
label_quad by
midangle.
function placeLabel(piece, angle)
{
var newangle, midangle;
var label_quad = document.createElement("div");
label_quad.className = "label_quad";
newangle = angle.newangle + parseInt(piece.piece);
midangle = ((parseInt(piece.piece)) / 2) + angle.newangle;
label_quad.style.WebkitTransform = "rotate(" + midangle + "deg)";
label_quad.style.transform = "rotate(" + midangle + "deg)";
var label_quad_wrapper = document.getElementById("label_quad_wrapper");
label_quad_wrapper.appendChild(label_quad);
var label = document.createElement("span");
label.className = "data_label";
label.innerHTML = piece.stats;
label_quad.appendChild(label);
return {"prevangle" : angle.newangle, "newangle": newangle};
}
There. Since 18 would occupy roughly a third of the pie (120 degrees), half of that is about 60 degrees, which is what we've rotated the div by!
Now let's add another label.
function displayData()
{
var stat = document.getElementById("ddlStat").value;
var row = document.getElementById("ddlRow").value;
var nonzero = getNonZero(row, stat);
var sorted = getSorted(nonzero);
var pieces = getPieces(sorted);
var lastPieceAngle = {"prevangle": 0, "newangle": 0};
var label_quad_wrapper = document.getElementById("label_quad_wrapper");
label_quad_wrapper.innerHTML = "";
lastPieceAngle = placeLabel(pieces[0], lastPieceAngle);
lastPieceAngle = placeLabel(pieces[1], lastPieceAngle);
}
Your next label is a 9. It takes up a fifth of the total of 51 goals, so its size is about 20% of the pie (roughly 70 degrees) and half of that is abut 35 degrees! 35 degrees from the last angle of 120 (or so) degrees, that is. If math isn't your thing or my explanation just sucks, worry not, The next part of this tutorial will make things visually clear.
Now, adding all these labels one by one is a pain in the ass, so let's put this in a
For loop.
function displayData()
{
var stat = document.getElementById("ddlStat").value;
var row = document.getElementById("ddlRow").value;
var nonzero = getNonZero(row, stat);
var sorted = getSorted(nonzero);
var pieces = getPieces(sorted);
var lastPieceAngle = {"prevangle": 0, "newangle": 0};
var label_quad_wrapper = document.getElementById("label_quad_wrapper");
label_quad_wrapper.innerHTML = "";
for (var i = 0; i < pieces.length; i++)
{
lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
}
}
There you go! But hell, it's devilishly hard to read the numbers when they're all rotated, so...
...let's rotate the label the other direction by the same number of degrees!
function placeLabel(piece, angle)
{
var newangle, midangle;
var label_quad = document.createElement("div");
label_quad.className = "label_quad";
newangle = angle.newangle + parseInt(piece.piece);
midangle = ((newangle - angle.newangle) / 2) + angle.newangle;
label_quad.style.WebkitTransform = "rotate(" + midangle + "deg)";
label_quad.style.transform = "rotate(" + midangle + "deg)";
var label_quad_wrapper = document.getElementById("label_quad_wrapper");
label_quad_wrapper.appendChild(label_quad);
var label = document.createElement("span");
label.className = "data_label";
label.innerHTML = piece.stats;
label.style.WebkitTransform = "rotate(" + (midangle * -1) + "deg)";
label.style.transform = "rotate(" + (midangle * -1) + "deg)";
label_quad.appendChild(label);
return {"prevangle" : angle.newangle, "newangle": newangle};
}
And we'll have to make the CSS class
data_label rotatable by giving it a
display property of inline-block. While we're there, let's make it rotate by its exact center by setting the
transform-origin property.
.data_label
{
color: #000000;
font-weight: bold;
font-family: verdana;
font-size: 0.8em;
position: relative;
display: inline-block;
-webkit-transform-origin: 50% 50%;
transform-origin: 50% 50%;
}
There you go.
Just set this back...
div {outline: 0px solid #FFAA00;}
Gettin' clear as shit!
Next
Time to start coloring the pie chart. You don't want to miss this!