﻿        function _piechart3D_curvewall(angle1, angle2, r, g, b) {

            var mx = this.cw / 2;
            var my = (this.ch - this.walldepth) / 2;
            var radx = mx;
            var rady = my;

            var sposx = mx + Math.cos(angle1 * Math.PI / 180.0) * radx;
            var sposy = my + Math.sin(angle1 * Math.PI / 180.0) * rady;
            var eposx = mx + Math.cos(angle2 * Math.PI / 180.0) * radx;
            var eposy = my + Math.sin(angle2 * Math.PI / 180.0) * rady;

            var steps = Math.round(Math.abs(this.stepsperdegree * (angle1 - angle2)));

            //draw the curved edge
            this._drawing.fillStyle(r, g, b);
            this._drawing.strokeStyle(r, g, b);

            //move to the bottom of the start of the 3D wall
            this._drawing.beginPath();
            this._drawing.moveTo(sposx, sposy);
            this._drawing.lineTo(sposx, sposy + this.walldepth);

            //draw an arc
            drawellipticalarcto(this._drawing, mx, my+this.walldepth, radx, rady, angle1 * Math.PI / 180.0, angle2 * Math.PI / 180.0);

            //line to the top of the end of the 3D wall
            this._drawing.lineTo(eposx, eposy);

            //draw an arc
            drawellipticalarcto(this._drawing, mx, my, radx, rady, angle2 * Math.PI / 180.0, angle1 * Math.PI / 180.0);

            this._drawing.closePath();
            this._drawing.fill();
            this._drawing.stroke();
        }
        function _piechart3D_top(angle1, angle2, r, g, b) {
            var mx = this.cw / 2;
            var my = (this.ch - this.walldepth) / 2;
            var radx = mx;
            var rady = my;

            var sposx = mx + Math.cos(angle1 * Math.PI / 180.0) * radx;
            var sposy = my + Math.sin(angle1 * Math.PI / 180.0) * rady;
            var eposx = mx + Math.cos(angle2 * Math.PI / 180.0) * radx;
            var eposy = my + Math.sin(angle2 * Math.PI / 180.0) * rady;

            var steps = Math.round(Math.abs(this.stepsperdegree * (angle1 - angle2)));

            //draw the top
            this._drawing.fillStyle(r, g, b);
            this._drawing.strokeStyle(r, g, b);
            this._drawing.beginPath();

            this._drawing.moveTo(mx, my);
            this._drawing.lineTo(sposx, sposy);
            drawellipticalarcto(this._drawing, mx, my, radx, rady, angle1 * Math.PI / 180.0, angle2 * Math.PI / 180.0);
            this._drawing.closePath();

            this._drawing.fill();
            this._drawing.stroke();
        }
        function _piechart3D_straightwall(angle1, angle2, r, g, b) {
            var mx = this.cw / 2;
            var my = (this.ch - this.walldepth) / 2;
            var radx = mx;
            var rady = my;

            var sposx = mx + Math.cos(angle1 * Math.PI / 180.0) * radx;
            var sposy = my + Math.sin(angle1 * Math.PI / 180.0) * rady;
            var eposx;
            var eposy;

            this._drawing.fillStyle(r, g, b);
            this._drawing.strokeStyle(r, g, b);

            //first side is not displayed when angle1 on right hand side
            //i.e. angle less than 90 or greater than 270
            if (angle1 > 90 && angle1 < 270) {
                this._drawing.beginPath();
                this._drawing.moveTo(Math.floor(sposx), Math.floor(sposy));
                this._drawing.lineTo(Math.floor(sposx), Math.floor(sposy + this.walldepth));
                this._drawing.lineTo(Math.floor(sposx), Math.floor(sposy + this.walldepth));
                this._drawing.lineTo(Math.floor(mx), Math.floor(my + this.walldepth));
                this._drawing.lineTo(Math.floor(mx), Math.floor(my));
                this._drawing.closePath();

                this._drawing.fill();
                this._drawing.stroke();
            }

            eposx = mx + Math.cos(angle2 * Math.PI / 180.0) * radx;
            eposy = my + Math.sin(angle2 * Math.PI / 180.0) * rady;

            if (angle2 < 90 || angle2 > 270) {
                this._drawing.beginPath();
                this._drawing.moveTo(Math.floor(eposx), Math.floor(eposy));
                this._drawing.lineTo(Math.floor(eposx), Math.floor(eposy + this.walldepth));
                this._drawing.lineTo(Math.floor(eposx), Math.floor(eposy + this.walldepth));
                this._drawing.lineTo(Math.floor(mx), Math.floor(my + this.walldepth));
                this._drawing.lineTo(Math.floor(mx), Math.floor(my));
                this._drawing.closePath();

                this._drawing.fill();
                this._drawing.stroke();
            }
        }

        function _piechart3D_drawslice(angle1, angle2, r, g, b) {
            //given that zero percent is at zero degrees, and that we work around from there,
            //there can never be a situation where a pieslice wraps around
            if (angle1 > angle2) { var t = angle2; angle2 = angle1; angle1 = t; }

            //draw the straight walls
//            this.drawstraightwall(angle1, angle2, r / 2, g / 2, b / 2);

            //draw the curved edge
            //only front of curved edge can be seen, in our coordinates, thats 0 to 180 degrees
            if (angle2 > 180) {
                if (angle1 < 180) {
                    this.drawcurvedwall(angle1, 180, r / 2, g / 2, b / 2);
                }
            } else {
                this.drawcurvedwall(angle1, angle2, r / 2, g / 2, b / 2);
            }

            //draw the top
            this.drawtop(angle1, angle2, r, g, b);
        }

        function _piechart3D_draw() {
            var a1, a2;

            //draw top right quadrant in forwards order
            for (var sang = 0, sliceidx = 0; sliceidx < this.slices.length; sliceidx++, sang = vang) {
                var slice = this.slices[sliceidx];
                var vang = (slice.v / this.total) * 360.0 + sang;

                if (vang > 360) { vang = 360 }

                if (vang > 270) {
                    a1 = Math.round(vang);
                    a2 = Math.round(sang); if (a2 < 270) { a2 = 270 }
                    this.drawpieslice(a1, a2, slice.c.r, slice.c.g, slice.c.b);
                }
            }
            //draw left half tracking backwards
            for (sang = 360, sliceidx = this.slices.length - 1; sliceidx >= 0; sliceidx--, sang = vang) {
                var slice = this.slices[sliceidx];
                var vang = sang - (slice.v / this.total) * 360.0;

                if (vang < 270 && sang > 90) {
                    a1 = vang; if (a1 < 90) { a1 = 90 }
                    a2 = sang; if (a2 > 270) { a2 = 270 }
                    this.drawpieslice(a1, a2, slice.c.r, slice.c.g, slice.c.b);
                }
            }
            //draw bottom right quadrant in forwards order
            for (var sang = 0, sliceidx = 0; sliceidx < this.slices.length; sliceidx++, sang = vang) {
                var slice = this.slices[sliceidx];
                var vang = (slice.v / this.total) * 360.0 + sang;

                if (vang > 360) { vang = 360 }

                if (sang < 90) {
                    a1 = Math.round(vang); if (a1 > 90) { a1 = 90 }
                    a2 = Math.round(sang);
                    this.drawpieslice(a1, a2, slice.c.r, slice.c.g, slice.c.b);
                }
            }

            this._drawing.end();
        }

        function _piechart_add(value) {
            this.total += Number(value);

            this.slices[this.slices.length] = {
                chart: this,
                v: value,
                c: this.colours[this.slices.length % 18],
                percentString: function() {
                    var n = Math.round(this.v / this.chart.total * 10000)/100;
                    return n + '%';
                }
            }
        }

        function PieChart3D(id, width, height, wd, style) {
            this.cw = width;
            this.ch = height;
            this.spanlength = 200;

            this._drawing = new Drawing(id, this.cw, this.ch, style);
            this.walldepth = wd;
            this.drawcurvedwall = _piechart3D_curvewall;
            this.drawtop = _piechart3D_top;
            this.drawstraightwall = _piechart3D_straightwall;
            this.drawpieslice = _piechart3D_drawslice;
            this.draw = _piechart3D_draw;
            this.add = _piechart_add;
            this.getitemcolour = function(i) { return this.slices[i].c; };
            this.setitemcolour = function(i, r, g, b) { this.slices[i].c = { r: r, g: g, b: b }; };
            this.getpercentstring = function(i) { return this.slices[i].percentString(); };

            this.slices = [];
            this.total = 0;
            this.colours = [
          { r: 185, g: 55, b: 39 },
          { r: 96, g: 97, b: 151 },
          { r: 113, g: 151, b: 162 },
          { r: 241, g: 213, b: 113 },
          { r: 13, g: 64, b: 68 },
          { r: 27, g: 96, b: 161 },
          { r: 200, g: 255, b: 160 },
          { r: 200, g: 200, b: 200 },
          { r: 255, g: 100, b: 100 },
          { r: 100, g: 200, b: 128 },
          { r: 50, g: 150, b: 220 },
          { r: 64, g: 64, b: 80 },
          { r: 180, g: 120, b: 50 },
          { r: 160, g: 255, b: 255 },
          { r: 150, g: 80, b: 150 },
          { r: 0, g: 100, b: 100 },

          { r: 128, g: 0, b: 128 },
          { r: 128, g: 128, b: 128 },
        ];

            var radx = this.cw / 2;
            var rady = (this.ch - this.walldepth) / 2;

            var sposx = Math.cos(90) * radx;
            var sposy = Math.sin(90) * rady;
            var eposx = Math.cos(91 * Math.PI / 180.0) * radx;
            var eposy = Math.sin(91 * Math.PI / 180.0) * rady;

            var dist = Math.sqrt((sposx - eposx) * (sposx - eposx) + (sposy - eposy) * (sposy - eposy));
            this.stepsperdegree = dist / this.spanlength;

            return this;
        }







        function vector(x1, y1, x2, y2) {
            return { x: x2 - x1, y: y2 - y1 };
        }

        function unitvector(x1, y1, x2, y2) {
            var dx = x2 - x1;
            var dy = y2 - y1;
            var length = Math.sqrt(dx * dx + dy * dy);

            return { x: dx / length, y: dy / length };
        }
        function unitperpendicularvector(x1, y1, x2, y2) {
            var uv = unitvector(x1, y1, x2, y2);

            return { x: -uv.y, y: uv.x };
        }
        function unitellipticaltangentvector(x1, y1, x2, y2, w, h) {
            var uv = unitvector(x1, y1, x2, y2);
            var tuv = unitvector(0, 0, -w * uv.y / h, h * uv.x / w);

            return { x: tuv.x, y: tuv.y };
        }
        function intersectlines(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) {
            var auv = vector(ax1, ay1, ax2, ay2);
            var buv = vector(bx1, by1, bx2, by2);
            var ag, bg;

            ag = auv.y / auv.x;
            bg = buv.y / buv.x;
            if (String(ag) == 'Infinity' || String(ag) == '-Infinity') {
                //ag is infinite, so line A is vertical
                //solve line B for x value of ax1
                var bzy = by1 - bx1 * buv.y / buv.x;
                return { x: ax1, y: bzy + buv.y / buv.x * ax1 };
            } else if (String(bg) == 'Infinity' || String(bg) == '-Infinity') {
                //bg is infinite, so line B is vertical
                //solve line A for x value of bx1
                var azy = ay1 - ax1 * auv.y / auv.x;
                return { x: bx1, y: azy + auv.y / auv.x * bx1 };
            } else {
                //solve y for x = 0
                var azy = ay1 - ax1 * auv.y / auv.x;
                var bzy = by1 - bx1 * buv.y / buv.x;

                //calculate x at point of intersection
                var x = (bzy - azy) / ((auv.y / auv.x) - (buv.y / buv.x));

                //solve y for value of x
                //var y = azy + auv.y / auv.x * x;
                var y = bzy + buv.y / buv.x * x;

                return { x: x, y: y };
            }
        }

        function drawellipticalarcto_inner(drawing, cx, cy, rx, ry, a1, a2) {
            var px1 = cx + Math.cos(a1) * rx;
            var py1 = cy + Math.sin(a1) * ry;

            var px2 = cx + Math.cos(a2) * rx;
            var py2 = cy + Math.sin(a2) * ry;

            var tx1 = cx + Math.cos(a1 + (a2 - a1) / 3) * rx * 5;
            var ty1 = cy + Math.sin(a1 + (a2 - a1) / 3) * ry * 5;
            var tx2 = cx + Math.cos(a2 - (a2 - a1) / 3) * rx * 5;
            var ty2 = cy + Math.sin(a2 - (a2 - a1) / 3) * ry * 5;

            var tn1 = unitellipticaltangentvector(cx, cy, px1, py1, rx, ry);
            var tn2 = unitellipticaltangentvector(cx, cy, px2, py2, rx, ry);

            var q1 = intersectlines(cx, cy, tx1, ty1, px1, py1, px1 + tn1.x, py1 + tn1.y);
            var q2 = intersectlines(cx, cy, tx2, ty2, px2, py2, px2 - tn2.x, py2 - tn2.y);

            drawing.bezierCurveTo(q1.x, q1.y, q2.x, q2.y, px2, py2);
        }
        function drawellipticalarcto(drawing, cx, cy, rx, ry, a1, a2) {
            //we need to get from angle a1 to angle a2 in steps not greater than 30 degrees
            if ((a2 > a1 && a2 - a1 > Math.PI * 2) || (a2 < a1 && a1 - a2 > Math.PI * 2)) {
                a2 = a1 + Math.PI * 2;
            }

            var step;
            var numsteps;

            if (Math.abs(a2 - a1) < Math.PI / 3) {
                numsteps = 1;
                step = a2 - a1;
            } else {
                numsteps = Math.ceil(Math.abs(a2 - a1) / (Math.PI / 3));
                step = Math.PI / 3;
                if (a2 - a1 < 0) { step = -step; }
            }

            var sa = a1;
            for (i = 0; i < numsteps; i++) {
                var ea = sa + step; if (i + 1 >= numsteps) { ea = a2; }

                drawellipticalarcto_inner(drawing, cx, cy, rx, ry, sa, ea);
                sa += step;
            }
        }

