Saturday 27 January 2018

Revenge of the Damore

Damore is back! And he's packing a mean wallop.

By "Damore", I mean, of course, James Damore, the software engineer formerly from Google, who, back last year, released a manifesto titled "Google's Ideological Echo Chamber". When this internal memo was leaked to the public, the predictable outcry from the "woke" and liberal crowd resulted in him being fired.

As mentioned in my last blogpost regarding this incident, my sympathies aren't with Damore. While I did not consider him guilty of what that rabid crowd accused him of, the sheer naivete in his writing and circulating of that (not even particularly insightful or well-written) manifesto deserved a harsh lesson. That lesson being; primarily, no matter how much your employers insist that they value honest and genuine feedback, no they really fucking don't. And, for quick and easy damage control, you can expect yourself to be handily thrown under the bus.

That was last August, and five months later, Damore is back with a proposed class-action lawsuit  against Google for discriminating against employees who hold politically Conservative views. Among his claims are that white men are being punished for, well, being white men.

"Google employees who expressed views deviating from the majority view at Google on political subjects raised in the workplace and relevant to Google's employment policies and its business, such as 'diversity' hiring policies, 'bias sensitivity,' or 'social justice' were/are singled out, mistreated, and systematically punished and terminated from Google, in violation of their legal rights."

There are also allegations of wrongful termination with regard to James Damore.

Hold on... "wrongful termination"?

As much as I think Google made themselves look utterly foolish with the reasons they gave for Damore's firing, "wrongful termination" is pushing it a little. Damore deserved to be canned, no doubt. As an employee, you are not guaranteed a right to absolute free speech on company time, and anyone who thinks otherwise is severely delusional.

Of course, it would have been even fairer if the miscreants who leaked his internal manifesto to the public (and thus forced Google's hand) were similarly dealt with, but one can't have everything.


Does James Damore stand any chance of winning?

I doubt that. But maybe this lawsuit isn't meant to be won.

Look at the evidence presented so far. Screenshots of discussions and whatnot. Many of them showcasing the shit people say when they think their vitriol isn't going to be made public. The sheer vindictiveness on display intertwined with their moral outrage. Vows to make life miserable for those with Conservative views. There's an entire cesspit of posturing, bullying militance in there, and it makes for depressing reading.

Just an example of the shit
Googlers post.

All of that is a huge embarrassment to Google. And if this particular humiliation was what James Damore was going for, then to hell with the lawsuit. Win or lose, the damage is done.

Google is under investigation for underpaying female staff. Now they're on trial for discriminating against white men. Which is it? It unlikely to be both - it's got to be one or the other. And it probably will be one or the other.

Well played, James. Well played.

Is Google really that much of a nightmare to work in? If those screenshots are anything to go by, it would appear so. I have no doubt that the Googlers are talented... but Christ on a stick, what a bunch of children they are. 

The Moral of the story

Damore-al of the story, geddit? (Hur hur)

Damore originally got into trouble because the manifesto got leaked to the public by Googlers. Now he's leaking screenshots of internal discussions by the legal process. Not saying I approve, but I guess the Googlers have it coming. Two can play at that game, right? And, let's face it, ammunition against Damore is limited. But he's got shit on all the people participating in those discussions. Whether or not the Googlers are sensible enough to realize that they come off as a bunch of utterly unprofessional, foot-stamping snowflakes, is besides the point. They, collectively, are a far bigger target than one James Damore.

Besides, it's not like James Damore has anything to lose at this point. Not after getting fired so publicly by Google.

Someone at Google said they "look forward to defending against Mr. Damore's lawsuit in court". Holy shit, so do I. This should be interesting.

Stay tuned for more news. Damore da merrier!
T___T

Tuesday 23 January 2018

App Review: ActiveSG

Today, I'll be reviewing a lifestyle app. It goes by the name of ActiveSG, and is actually the mobile app version of the site at www.active.sg, by iApps Pte Ltd.



While I said this is a lifestyle app, it's not strictly in that category as far as functionality is concerned. For starters, there is nothing to monitor your calorie intake, exercise habits, and stuff like that. What it really is, is a booking app for Singapore Sports Council's facilities.


Simple enough, right? Can't go wrong with simple, right? Well, ActiveSG somehow manages to screw that up. Read on...

The Premise

With ActiveSG, you can book facilities such as tennis courts, swimming pools and gyms. You can even pay or renew memberships through the app, and the QR code in your profile will be used at gantries to determine what access you have.

The Aesthetics

ActiveSG scores horribly in this in my estimation. The makers of this app are guilty of trying a little too hard with garish colors coupled with the whole neon text on black background dynamic that just comes across as a little clumsy. Honestly, whoever thought lime green on black was cool, needs to be shot.







The Experience

Had difficulty moving around. The app was hard to navigate, and finding what I needed came only with frequent practice. There were times when attempting to go back to the previous screen almost resulted in exiting the application altogether.


The Interface

Confusing as hell in certain spots. The sheer overload of varying colors sure didn't help. What can be clicked? What can't? Very little of it was clear and had to be sorted out via trial and error.

What I liked

The QR Code for getting past the gantry, is at least conveniently placed, I guess.

The fact that I don't have to use this shitty app every day, is a bonus.

What I didn't

Color scheme stinks.

What's with the extraneous welcome message? Pro-tip: Anything that forces the user to tap extra times, is a huge no-no.



Navigation is crap. I mean, seriously, fuck your multiple-level menus.


App gives you a timeout message even if you logged out properly the last time.



App has a nasty habit of crashing.

If you attempt to click on your QR Code when your profile picture hasn't loaded yet, it will give you a timeout error message. Even if you've been using the app for the past five minutes.



Conclusion

For an app that purports to do so little, ActiveSG has a lot of room for improvement. The overall layout needs to change and I would suggest something that's a little less overwhelming and confusing. Provides nothing that the ActiveSG site doesn't already do. Save your phone space. Use the URL instead.

My Rating

3 / 10

Terrible offering. App-alling, even.
T___T

Thursday 18 January 2018

Web Tutorial: The Pie Chart (Part 2/2)

Welcome back for the second part of this tutorial. Now that you've put up the labels, a lot of the functionality is already there and we just need to leverage on it. Much of this web tutorial is going to resemble the Wheel of Fortune we made a couple years back - except that instead of equal-sized pie slices, the pie slices here will likely be of different sizes.

First though, modify your CSS. We need to put the outlines back, but only for stuff we're adding into the pie_container div.
#pie_container, #pie_container div {outline: 1px solid #FFAA00;}


Got that? Great.


Here, add this code to clear the quad_wrapper_left and quad_wrapper_right divs. They're already empty right now, so you won't see any change. Also, we will define quads as an empty array and lastQuadAngle as an object, much the same way we did with pieces and lastPieceAngle.
            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 quads=[];
                var lastQuadAngle = {"prevangle":0,"newangle":0};

                var quad_wrapper;
                quad_wrapper = document.getElementById("quad_wrapper_right");
                quad_wrapper.innerHTML = "";
                quad_wrapper.style.backgroundColor = "";
                quad_wrapper = document.getElementById("quad_wrapper_left");
                quad_wrapper.innerHTML = "";

                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);
                }
            }


Now, in the For loop, we'll want to start presenting the data in the pieces array. First, set quads to the returned value of the getQuads() function, passing in the current value of pieces as an argument.
                for (var i = 0; i < pieces.length; i++)
                {
                    quads = getQuads(pieces[i]);

                    lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
                }


And create the getQuads() function.

Why is this necessary? Don't we already have the pieces?

Well, see, the pieces array give us the pie slice angles, colors and statistics. But we're dealing with HTML elements here and basically they're all squares. And each div can give you 90 degrees at most. Thus, if your angle is more than 90 degrees, you'll need to add more quads!

For example:

You have a 150 degree pie slice. You need a 90 degree quad, and another 90 degree quad rotated by 150 - 90 = 60 degrees to form the full 150 degrees.


You have a 190 degree pie slice. You need a 90 degree quad, another 90 degree quad, and a third 90 degree quad  rotated by 190 - 90 - 90 = 10 degrees to form the full 190 degrees.


You have a 285 degree pie slice. You need a 90 degree quad, another 90 degree quad a third 90 degree quad and a fourth 90 degree quad  rotated by 285 - 90 - 90 - 90 = 15 degrees to form the full 285 degrees.

OK, so back to the getQuads() function...

Here, quads is defined as an empty array, rem (short for "remainder") is the piece property of the piece object passed in, and quads is returned at the end of the function.
            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.style.WebkitTransform = "rotate(" + (midangle * -1) + "deg)";
                label.style.transform = "rotate(" + (midangle * -1) + "deg)";
                label_quad.appendChild(label);

                return {"prevangle" : angle.newangle, "newangle": newangle};
            }

            function getQuads(piece)
            {
                var quads = [];
                var rem = piece.piece;

                return quads;
            }

            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;
            }


Now, this section of code will last as long as the value of rem is more than 0. With each iteration, rem will be decremented by the maximum size of one quad, which is 90 degrees.
            function getQuads(piece)
            {
                var quads = [];
                var rem = piece.piece;

                while (rem>0)
                {               
                    rem = rem - 90;
                }

                return quads;
            }


Now we check if rem is less than or equal to 90 degrees. If so, push an object into the quads array with the color and the value of rem. If not, push a full quad into quads.
            function getQuads(piece)
            {
                var quads = [];
                var rem = piece.piece;

                while (rem>0)
                {                   
                    if ((rem <= 90)
                    {
                        quads.push
                        (
                            {
                                "color":piece.color,
                                "piece":rem
                            }
                        );
                    }
                    else
                    {
                        quads.push
                        (
                            {
                                "color":piece.color,
                                "piece":90
                            }
                        );
                    }

                    rem = rem - 90;
                }

                return quads;
            }


So if you had a piece that was 300 degrees and the color was #FF00FF, you would pass this into the getQuads() function...
    {
        "color": "#FF00FF",
        "piece": 300
    }


... and the quads array would look like this when you were done.
[
    {
        "color": "#FF00FF",
        "piece": 90   
    },
    {
        "color": "#FF00FF",
        "piece": 90   
    },
    {
        "color": "#FF00FF",
        "piece": 90   
    },
    {
        "color": "#FF00FF",
        "piece": 30   
    }
]



Back to the displayData() function, go to the For loop. Iterate through the quads array.
                for (var i = 0; i < pieces.length; i++)
                {
                    quads = getQuads(pieces[i]);

                    for (var j = 0; j < quads.length; j++)
                    {

                    }

                    lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
                }


Set lastQuadAngle by running the placeQuad() function. Pass in the formula (1000 - (i * 10)) - j, the current element in quads, and the value of lastQuadAngle (which is initially 0). The formula is meant to determine the z-index property of each quad so they don't overlap in an awkward way.
                for (var i = 0; i < pieces.length; i++)
                {
                    quads = getQuads(pieces[i]);

                    for (var j = 0; j < quads.length; j++)
                    {
                        lastQuadAngle = placeQuad((1000 - (i * 10)) - j, quads[j], lastQuadAngle);
                    }

                    lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
                }


Now this is the final function we will be working on. The end is in sight! Create the placeQuad() function. It will accept three parameters - zindex, quad, and angle.
            function placeQuad(zindex, quad, angle)
            {

            }

            function getQuads(piece)
            {
                var quads = [];
                var rem = piece.piece;

                while (rem>0)
                {                   
                    if ((rem <= 90)
                    {
                        quads.push
                        (
                            {
                                "color":piece.color,
                                "piece":rem
                            }
                        );
                    }
                    else
                    {
                        quads.push
                        (
                            {
                                "color":piece.color,
                                "piece":90
                            }
                        );
                    }

                    rem = rem - 90;
                }

                return quads;
            }


First, declare the variable newangle. Then create a div element and set it to another variable, pie_quad. Set its properties using the quad object's color property, and zindex. It will be styled using the CSS class pie_quad.
            function placeQuad(zindex, quad, angle)
            {
                var newangle;
                var pie_quad = document.createElement("div");
                pie_quad.className = "pie_quad";
                pie_quad.style.backgroundColor = quad.color;
                pie_quad.style.zIndex = zindex;
            }


Let's create the CSS class pie_quad. Since it's a square that will take up half the area of its parent quad_wrapper (which in turn will take half half of the square div pie_container), width is 100% and height is 50%. margin-bottom is set to negative 100% because there will be multiple divs styled by pie_quad inside quad_wrapper, and they all have to overlap. We explicitly define the position property as relative and set the rotation point to the bottom left.
            .quad_wrapper
            {
                width: 50%;
                height: 100%;
                float: left;
            }

            .pie_quad
            {
                width: 100%;
                height: 50%;
                margin-bottom: -100%;
                -webkit-transform-origin: 0% 100%;
                transform-origin: 0% 100%;
                position: relative;
            }

            #label_wrapper
            {
                height: 100%;
                width: 100%;
                margin-right: -100%;
                float: left;
                position: relative;
                z-index: 2000;
            }


Now back to the placeQuad() function. Set newangle to the previous angle (the newangle property of the angle object) plus the value of the quad object's piece property. Then rotate pie_quad by that value minus 90 degrees.

Why?

See, if the new finishing angle is exactly 90 degrees, then we wouldn't need to rotate it at all. If it's more than 90 degrees, then we only need to rotate pie_quad by that difference, for the pie_quad to end up at exactly newangle degrees. If it's less than 90 degrees, then we rotate counter-clockwise.

Also, ensure that the function returns an object containing the previous and newest angles.
            function placeQuad(zindex, quad, angle)
            {
                var newangle;
                var pie_quad = document.createElement("div");
                pie_quad.className = "pie_quad";
                pie_quad.style.backgroundColor = quad.color;
                pie_quad.style.zIndex = zindex;

                newangle = angle.newangle + parseInt(quad.piece);
                pie_quad.style.WebkitTransform = "rotate(" + (newangle - 90) + "deg)";
                pie_quad.style.transform = "rotate(" + (newangle - 90) + "deg)";

                return {"prevangle": angle.newangle, "newangle": newangle};
            }


Now, if the previous angle was more than 180 degrees, then we append pie_quad to quad_wrapper_left. If not, we append it to quad_wrapper_right.
            function placeQuad(zindex, quad, angle)
            {
                var newangle;
                var pie_quad = document.createElement("div");
                pie_quad.className = "pie_quad";
                pie_quad.style.backgroundColor = quad.color;
                pie_quad.style.zIndex = zindex;

                newangle = angle.newangle + parseInt(quad.piece);
                pie_quad.style.WebkitTransform = "rotate(" + (newangle - 90) + "deg)";
                pie_quad.style.transform = "rotate(" + (newangle - 90) + "deg)";

                var quad_wrapper;

                if (angle.newangle >= 180)
                {
                    quad_wrapper = document.getElementById("quad_wrapper_left");
                    quad_wrapper.appendChild(pie_quad);
                }
                else
                {
                    quad_wrapper = document.getElementById("quad_wrapper_right");
                    quad_wrapper.appendChild(pie_quad);
                }

                return {"prevangle": angle.newangle, "newangle": newangle};
            }


We're going to test our code. Back to the displayData() function, change this so that only the first element of pieces and the first element of quads gets processed.
                for (var i = 0; i <= 0; i++)
                {
                    quads = getQuads(pieces[i]);

                    for (var j = 0; j <= 0 ; j++)
                    {
                        lastQuadAngle = placeQuad((1000 - (i * 10)) - j, quads[j], lastQuadAngle);
                    }

                    lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
                }


Look up the goals for 2009 to 2010. The first piece represents Fernando Torres, who scored the most number of goals at 18 out of a total of 51. This will take up more than a quarter of the circle, so why is the piece at exactly one quarter? That's because we've only processed one quad out of that piece!


Change the code to process all the quads of the first pieces element.
                for (var i = 0; i <= 0; i++)
                {
                    quads = getQuads(pieces[i]);

                    for (var j = 0; j < quads.length; j++)
                    {
                        lastQuadAngle = placeQuad((1000 - (i * 10)) - j, quads[j], lastQuadAngle);
                    }

                    lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
                }


This piece has two quads - one from 0 to 90 degrees and the other at 37 to 127 degrees. So now we have a piece that appears to go from 0 to 127 degrees!


Now let's process the next piece.
                for (var i = 0; i <= 1; i++)
                {
                    quads = getQuads(pieces[i]);

                    for (var j = 0; j < quads.length; j++)
                    {
                        lastQuadAngle = placeQuad((1000 - (i * 10)) - j, quads[j], lastQuadAngle);
                    }

                    lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
                }


Next is Dirk Kuyt at 9 goals. Since 9 is less than a quarter of 51, there is only one quad and it should take up 64 degrees. We place the quad, then rotate it clockwise until the final finishing angle is (127 + 64 = 191) degrees, which means we have to rotate it by (191 - 90 = 101) degrees. Since its z-index property is programmatically lower than the previous quad's, the rest of the yellow square gets cut off. Which is exactly what we want!


Let's push our luck with the next piece...
                for (var i = 0; i <= 2; i++)
                {
                    quads = getQuads(pieces[i]);

                    for (var j = 0; j < quads.length; j++)
                    {
                        lastQuadAngle = placeQuad((1000 - (i * 10)) - j, quads[j], lastQuadAngle);
                    }

                    lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
                }


Steven Gerrard also scored 9 goals, so his piece should be the same size as Dirk Kuyt's. But holy crap, what happened here?


Remember that if the last angle is more than 180 degrees, then the piece gets appended to quad_wrapper_left instead of quad_wrapper_right! So we need to add an adjustment to the CSS...

If the div styled by pie_quad is inside the quad_wrapper_left div, we push it 100% to the right!.
            .quad_wrapper
            {
                width: 50%;
                height: 100%;
                float: left;
            }

            .pie_quad
            {
                width: 100%;
                height: 50%;
                margin-bottom: -100%;
                -webkit-transform-origin: 0% 100%;
                transform-origin: 0% 100%;
                position: relative;
            }

            #quad_wrapper_left .pie_quad
            {
                margin-left: 100%;
            }


There ya go. But it's still not correct because the divs aren't the same size. That's because the yellow div is overlapping the brown one.


So here we ensure that nothing overflows from divs styled with quad_wrapper.
            .quad_wrapper
            {
                width: 50%;
                height: 100%;
                float: left;
                overflow: hidden;
            }


This is still incorrect because now the brown div is larger than the yellow div, and they're supposed to be of the same size! The end angle is correct though.


What we need here is to handle the case where the quad starts before 180 degrees and ends after.
            function placeQuad(zindex, quad, angle)
            {
                var newangle;
                var pie_quad = document.createElement("div");
                pie_quad.className = "pie_quad";
                pie_quad.style.backgroundColor = quad.color;
                pie_quad.style.zIndex = zindex;

                newangle = angle.newangle + parseInt(quad.piece);
                pie_quad.style.WebkitTransform = "rotate(" + (newangle - 90) + "deg)";
                pie_quad.style.transform = "rotate(" + (newangle - 90) + "deg)";

                var quad_wrapper;

                if (angle.newangle >= 180)
                {
                    quad_wrapper = document.getElementById("quad_wrapper_left");
                    quad_wrapper.appendChild(pie_quad);
                }
                else
                {
                    quad_wrapper = document.getElementById("quad_wrapper_right");
                    quad_wrapper.appendChild(pie_quad);

                    if (newangle >= 180)
                    {

                    }
                }

                return {"prevangle": angle.newangle, "newangle": newangle};
            }


Make a div, call it pie_quad_copy and style it identically to pie_quad. Then rotate it and append to quad_wrapper_left.
            function placeQuad(zindex, quad, angle)
            {
                var newangle;
                var pie_quad = document.createElement("div");
                pie_quad.className = "pie_quad";
                pie_quad.style.backgroundColor = quad.color;
                pie_quad.style.zIndex = zindex;

                newangle = angle.newangle + parseInt(quad.piece);
                pie_quad.style.WebkitTransform = "rotate(" + (newangle - 90) + "deg)";
                pie_quad.style.transform = "rotate(" + (newangle - 90) + "deg)";

                var quad_wrapper;

                if (angle.newangle >= 180)
                {
                    quad_wrapper = document.getElementById("quad_wrapper_left");
                    quad_wrapper.appendChild(pie_quad);
                }
                else
                {
                    quad_wrapper = document.getElementById("quad_wrapper_right");
                    quad_wrapper.appendChild(pie_quad);

                    if (newangle >= 180)
                    {
                        var pie_quad_copy = document.createElement("div");
                        pie_quad_copy.className = "pie_quad";
                        pie_quad_copy.style.backgroundColor = quad.color;
                        pie_quad_copy.style.zIndex = zindex;


                        pie_quad_copy.style.WebkitTransform = "rotate(" + (newangle - 90) + "deg)";                         pie_quad_copy.style.transform = "rotate(" + (newangle - 90) + "deg)";

                        quad_wrapper = document.getElementById("quad_wrapper_left");
                        quad_wrapper.appendChild(pie_quad_copy);
                    }
                }

                return {"prevangle": angle.newangle, "newangle": newangle};
            }


Now they're equal.


Do the rest!
                for (var i = 0; i < pieces.length; i++)
                {
                    quads = getQuads(pieces[i]);

                    for (var j = 0; j < quads.length; j++)
                    {
                        lastQuadAngle = placeQuad((1000 - (i * 10)) - j, quads[j], lastQuadAngle);
                    }

                    lastPieceAngle = placeLabel(pieces[i], lastPieceAngle);
                }


Reset this!
#pie_container, #pie_container div {outline: 0px solid #FFAA00;}


You have a pie chart! Try changing the values of the drop-down lists. Does the data change?


Final Notes

You may have noticed two things.

Firstly, it's always largest to smallest piece. Secondly, the statistic always starts and ends with 0 degrees.

These are deliberate. We purposely ordered the elements and ensured that everything begins from angle zero, and not from anywhere else. So we will always be certain that the next piece is equal-sized or smaller, and if the current piece is more than 180 degrees in size, there is mathematically no way that the next piece is not smaller. So we cut off that case right off the bat.

What a tutorial, eh? I quad enjoyed it!
T___T

Tuesday 16 January 2018

Web Tutorial: The Pie Chart (Part 1/2)

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!