Graph.as
DataPointSet.as
DataPoint.as


/*
author: Sammy Joe Osborne
date: 09/15/06
Graph Class
Renders a graph to the stage using a blank movie clip in the library as it's base.
Parameters are in the following order:
Width of graph including margin, Height of graph including margin, x axis maximum number, y axis maximum number, x axis step number, y axis step number

All of this can/should? later be calculated automatically based on the data being entered, then accessible to change via
get and set methods.
*/
import mx.transitions.Tween;
import mx.transitions.easing.*;
import flash.geom.*;

class Graph extends MovieClip{
        //Constants
        private var decimalRound:Number = 100; //number to round decimals.  100 to round to 100's place, etc.
        
        //private instance variables
        private var dataSetCount:Number = 0; //the number of sepparate sets of data (lines) being graphed on this graph
        private var xUnit:Number; //the number in pixels equal to one x unit on the graph
        private var yUnit:Number; //the number in pixels equal to one y unit on the graph
        private var yMargin:Number; //the number in pixels between the edge of the movie and the actual start of the graphing area
        private var xMargin:Number; //the number in pixels between the edge of the movie and the actual start of the graphing area
        private var gridLinesOn:Boolean; //whether gridlines should be on or not
        private var xAxisMax:Number; //highest number to display on the x axis
        private var yAxisMax:Number; //highest number to display on the y axis
        private var xAxisIncrement:Number; //number to increment the next mark on the x axis
        private var yAxisIncrement:Number; //number to increment the next mark on the y axis
        private var xAxisIncrementPixels:Number; //number in pixels to increment on the x axis
        private var yAxisIncrementPixels:Number; //number in pixels to increment on the y axis
        private var bgColor:Number; //background color for graph
        private var graphHeight:Number; //the height of the graph, including margins
        private var graphWidth:Number; //the width of the graph, including margins
        private var xDashLength:Number; //the length in pixels of the dashes used on the x axis to show increments
        private var yDashLength:Number; //the length in pixels of the dashes used on the y axis to show increments
        private var xGraphGridLines:Boolean = false; //true for x axis (verticle) grid lines on, false for off.  Default is false
        private var yGraphGridLines:Boolean = true; //true for y axis (horizontile) grid lines on, false for off.  Default is true
        private var graphGridLines:String = "y"; //Turns gridlines for x, y, or both on or off.  Valid values are: "x", "y", "both", or "none".  Default is "y".
        private var xAxisDashes:Boolean = true; //turns x Axis ticks either on or off.  Default is on
        private var yAxisDashes:Boolean = true; //turns y Axis ticks either on or off.  Default is on
        private var graphBackgroundType:String; //the type of background used in the graph.  Can only be set through the fillBackground() method
        private var dotColor:String; //determines the dot style (color) of dot for all dots created after this is set
        private var lineColor:String; //determines the line style (color) for all lines created after this is set
        private var dataSets:Array = new Array(); //an array to hold the names of all dataSets on the graph
        
        
        //constructor
        public function Graph(){
                trace("Graph created");
        }
        
        
        public function init(xSize:Number, ySize:Number, _xAxisMax:Number, _yAxisMax:Number, _xAxisIncrement:Number, _yAxisIncrement:Number){
                xMargin = 30;
                yMargin = 30;
                graphWidth = xSize;
                graphHeight = ySize;
                xAxisMax = _xAxisMax;
                yAxisMax = _yAxisMax;
                xAxisIncrement = _xAxisIncrement;
                yAxisIncrement = _yAxisIncrement;
                yUnit = int(((graphHeight - yMargin) / yAxisMax)*decimalRound)/decimalRound;
                xUnit = int(((graphWidth - xMargin) / xAxisMax)*decimalRound)/decimalRound;
                xAxisIncrementPixels = int((xUnit * xAxisIncrement)*decimalRound)/decimalRound;
                yAxisIncrementPixels = int((yUnit * yAxisIncrement)*decimalRound)/decimalRound;
                yDashLength = 5;
                xDashLength = 5;
                trace("Graph initialized");
                
                
                drawGraph();
                
                return this;
        }
        
        public function updateGraph(_xAxisMax:Number, _yAxisMax:Number, _xAxisIncrement:Number, _yAxisIncrement:Number){
                xAxisMax = _xAxisMax;
                yAxisMax = _yAxisMax;
                xAxisIncrement = _xAxisIncrement;
                yAxisIncrement = _yAxisIncrement;
                yUnit = int(((graphHeight - yMargin) / yAxisMax)*decimalRound)/decimalRound;
                xUnit = int(((graphWidth - xMargin) / xAxisMax)*decimalRound)/decimalRound;
                xAxisIncrementPixels = int((xUnit * xAxisIncrement)*decimalRound)/decimalRound;
                yAxisIncrementPixels = int((yUnit * yAxisIncrement)*decimalRound)/decimalRound;
                
                
        }
        
        //------------------------------------Graphing Rendering Functions-----------------------------------------
        
        private function drawGraph(){
                //initializes height and width by drawing blank lines
                moveTo(0, 0);
                lineTo(graphWidth, 0);
                moveTo(0, 0);
                lineTo(0, graphHeight);
                
                drawOutlines();
                this.fillBackground("gradient", 0xcccccc, 0x999999);
                drawGridLines();
                drawXAxisDashes();
                drawYAxisDashes();
                drawXAxisNumbers();
                drawYAxisNumbers();
                
                //NOTE: DELETE THIS.  Temporary to show margins
                /*moveTo(0,0);
                lineTo(0, graphHeight);
                lineTo(graphWidth, graphHeight);
                trace("Height: " + _height);
                trace("Width: " + _width);*/
        }
        
        //fills in a background with specified color. Default is 0xDBDBDB.  Doesn't fill if "none" is specified by user
        public function fillBackground(_type:String, _color1:Number, _color2:Number):Void{
                graphBackgroundType = _type;
                var movieName:String = this._name + "Background";
                if(this[movieName]._x == undefined){
                        this.createEmptyMovieClip(movieName, this.getNextHighestDepth());
                }
                this[movieName].clear();
                
                if(graphBackgroundType == "none"){
                        this[movieName].clear();
                }
                else if(graphBackgroundType == "solid"){
                        var color:Number = _color1;
                        this[movieName].clear();
                        this[movieName].moveTo(xMargin+1, 0);
                        this[movieName].beginFill(color, 100);
                        this[movieName].lineTo(xMargin+1, (graphHeight - yMargin-1));
                        this[movieName].lineTo(graphWidth, (graphHeight - yMargin-1));
                        this[movieName].lineTo(graphWidth, 0);
                        this[movieName].lineTo(xMargin+1, 0);
                        this[movieName].endFill();
                }
                else if(graphBackgroundType == "gradient"){
                        var colors:Array = [_color1, _color2];
                        this[movieName].clear();
                        var fillType:String = "linear"
                        var alphas:Array = [100, 100];
                        var ratios:Array = [0, 255];
                        var matrix:Matrix = new Matrix();
                        matrix.createGradientBox(graphWidth - xMargin, graphHeight - yMargin, -67.5, 0, 0);
                        var spreadMethod:String = "pad";
                        this[movieName].moveTo(xMargin+1, 0);
                        this[movieName].beginGradientFill(fillType, colors, alphas, ratios, matrix, spreadMethod);
                        this[movieName].lineTo(graphWidth, 0);
                        this[movieName].lineTo(graphWidth, (graphHeight - yMargin-1));
                        this[movieName].lineTo(xMargin+1, (graphHeight - yMargin-1));
                        this[movieName].lineTo(xMargin+1, 0);
                        this[movieName].endFill();
                }
                else{trace("you must enter either \"none\", \"solid\", or \"gradient\" in the background fill function.");}
        }
        
        
        //draws graph axes lines
        private function drawOutlines():Void{
                var movieName:String = this._name + "Outlines";
                if(this[movieName]._x == undefined){
                        this.createEmptyMovieClip(movieName, this.getNextHighestDepth());
                }
                this[movieName].clear();
                this[movieName].moveTo(xMargin, -1);
                this[movieName].lineStyle(2, 0x000000, 100, false, "none", "none");
                this[movieName].lineTo(xMargin, (graphHeight - yMargin));
                this[movieName].lineTo(graphWidth + 1, (graphHeight - yMargin));
        }
        
        
        //draw x axis increment dashes (vertical)
        private function drawXAxisDashes():Void{
                var movieName = this._name + "XAxisDashes";
                if(this[movieName]._x == undefined){
                        this.createEmptyMovieClip(movieName, this.getNextHighestDepth());
                }
                this[movieName].clear();
                if(xAxisDashes == true){
                        this[movieName].lineStyle(2, 0x000000, 100, false, "none", "none");
                        var i = 0;
                        for(var j = xAxisIncrementPixels; i <= (xAxisMax / xAxisIncrement)-1; j += xAxisIncrementPixels){
                                this[movieName].moveTo((xMargin + j), (graphHeight - yMargin));
                                this[movieName].lineTo((xMargin + j), (graphHeight - yMargin + xDashLength));
                                i++;
                        }
                }
        }
        
        
        //draw y axis increment dashes (horizontile)
        private function drawYAxisDashes():Void{
                var movieName = this._name + "YAxisDashes";
                if(this[movieName]._x == undefined){
                        this.createEmptyMovieClip(movieName, this.getNextHighestDepth());
                }
                this[movieName].clear();
                if(yAxisDashes == true){
                        this[movieName].lineStyle(2, 0x000000, 100, false, "none", "none")
                        var j = 0;
                        for(var i = 0; j <= (yAxisMax / yAxisIncrement); i += yAxisIncrementPixels){
                                this[movieName].moveTo(xMargin, (0 + i));
                                this[movieName].lineTo((xMargin - yDashLength),(0 + i));
                                j++;
                        }
                }
        }
        
        
        //draw numbers along x axis
        private function drawXAxisNumbers():Void{
                var movieName = this._name + "XAxisNumbers";
                if(this[movieName]._x != undefined){
                        //recreates the movieclip the numbers are housed in and loads it over the old one, incase they need to be updated
                        this.createEmptyMovieClip(movieName, this[movieName].getDepth());
                }
                else{
                        this.createEmptyMovieClip(movieName, this.getNextHighestDepth());
                }
                var axisTextFormat:TextFormat = new TextFormat();
                axisTextFormat.font = "Arial";
                axisTextFormat.size = 14;
                var xAxisText:Number = Number(xAxisIncrement);
                var i = 0;
                for(var j = xAxisIncrementPixels; i <= (xAxisMax / xAxisIncrement)-1; j += xAxisIncrementPixels){
                        this[movieName].createTextField(("xAxisNum"+j), this[movieName].getNextHighestDepth(), (xMargin + j), (graphHeight - yMargin + yDashLength), 10, 100);
                        this[movieName]["xAxisNum" + j].autoSize = "left"
                        this[movieName]["xAxisNum" + j].setNewTextFormat(axisTextFormat);
                        this[movieName]["xAxisNum" + j].text = xAxisText;
                        this[movieName]["xAxisNum" + j]._x = this[movieName]["xAxisNum" + j]._x - (this[movieName]["xAxisNum" + j]._width / 2); //aligns text properly to the dash it's positioned next to
                        xAxisText += Number(xAxisIncrement); //have to use casting for some reason or it appends it like a string
                        i++;
                }
        }
        
        
        //draw numbers along y axis
        private function drawYAxisNumbers():Void{
                var movieName = this._name + "YAxisNumbers";
                if(this[movieName]._x != undefined){
                        //recreates the movieclip the numbers are housed in and loads it over the old one, incase they need to be updated
                        this.createEmptyMovieClip(movieName, this[movieName].getDepth());
                }
                else{
                        this.createEmptyMovieClip(movieName, this.getNextHighestDepth());
                }
                var axisTextFormat:TextFormat = new TextFormat();
                axisTextFormat.font = "Arial";
                axisTextFormat.size = 14;
                var yAxisText:Number = 0;
                var i = 0;
                for(var j = 0; i <= (yAxisMax / yAxisIncrement); j += yAxisIncrementPixels){
                        this[movieName].createTextField(("yAxisNum"+j), this[movieName].getNextHighestDepth(), (xMargin - xDashLength - 10), (graphHeight - yMargin - j), 10, 100);
                        this[movieName]["yAxisNum" + j].autoSize = "right";
                        this[movieName]["yAxisNum" + j].setNewTextFormat(axisTextFormat);
                        this[movieName]["yAxisNum" + j].text = yAxisText;
                        this[movieName]["yAxisNum" + j]._y = this[movieName]["yAxisNum" + j]._y - (this[movieName]["yAxisNum" + j]._height / 2); //aligns text properly to the dash it's positioned next to
                        yAxisText += Number(yAxisIncrement); //have to use casting for some reason or it appends it like a string
                        i++;
                }
        }
        
        
        //draws gridlines
        private function drawGridLines():Void{
                drawXGridLines();
                drawYGridLines();
                this[this._name + "XGridLines"]._visible = false;
                this[this._name + "YGridLines"]._visible = false;
                if(xGraphGridLines == true){
                        this[this._name + "XGridLines"]._visible = true;
                }
                if(yGraphGridLines == true){
                        this[this._name + "YGridLines"]._visible = true;
                }
        }
        
        
        //draw x axis grid lines (vertical)
        private function drawXGridLines():Void{
                var movieName:String = this._name + "XGridLines";
                if(this[movieName]._x == undefined){
                        this.createEmptyMovieClip(movieName, this.getNextHighestDepth());
                }
                
                this[movieName].clear();
                
                this[movieName].lineStyle(1, 0x000000, 100, false, "none", "none");
                var i = 0;
                for(var j = 0; i <= (xAxisMax / xAxisIncrement); j += xAxisIncrementPixels){
                        this[movieName].moveTo((xMargin + j), (graphHeight - yMargin));
                        this[movieName].lineTo((xMargin + j), 0);
                        i++;
                }
        }
        
        
        //draw y axis grid lines (horizontile)
        private function drawYGridLines():Void{
                var movieName:String = this._name + "YGridLines";
                if(this[movieName]._x == undefined){
                        this.createEmptyMovieClip(movieName, this.getNextHighestDepth());
                }
                
                this[movieName].clear();
                
                this[movieName].lineStyle(1, 0x000000, 100, false, "none", "none");
                var j = 0;
                for(var i = 0; j <= (yAxisMax / yAxisIncrement); i += yAxisIncrementPixels){
                        this[movieName].moveTo(xMargin, (0 + i));
                        this[movieName].lineTo(graphWidth,(0 + i));
                        j++;
                }
        }
        
        //--------------------------------------Graphing Functions-----------------------------------------
        public function addLine(lineName:String, lineColor:Object){
                //make sure the line gets a color of blue if a color isn't specified
                if(lineColor == undefined){lineColor = "0x66cc00";}
                //checks to see if the line already exists, and if so, just create it again over top of the old one
                if(this[lineName]._x != undefined){
                        this.attachMovie("DataPointSet", lineName, this[lineName].getDepth(), {x:0, y:0}).init(lineColor);
                }
                //otherwise, if it doesn't exist, create it at the next highest depth
                else{
                        this.attachMovie("DataPointSet", lineName, this.getNextHighestDepth(), {x:0, y:0}).init(lineColor);
                        dataSets[dataSetCount] = lineName;//add the name of the line to the dataSets array
                        dataSetCount++;//up the number of lines (sets of data) the graph has
                }
        }
        
        public function addDataPoint(lineName:String, _xCoordinate:Number, _yCoordinate:Number):Void{
                var graphStats:Object = new Object();
                graphStats.xMargin = xMargin;
                graphStats.yMargin = yMargin;
                graphStats.xUnit = xUnit;
                graphStats.yUnit = yUnit;
                graphStats.graphHeight = graphHeight;
                this[lineName].addDataPoint(_xCoordinate, _yCoordinate, graphStats);
                this[lineName].drawPoints();
                if(this[lineName].pointCount > 1){
                        this[lineName].drawLines();
                }
        }
        
        public function animateLines(lineToMove:String, goal:String){
                //this calls draw lines every 20 milliseconds to keep up with the tweening of the points
                var intervalID:Number = setInterval(this[lineToMove], "drawLines", 20);
                this[lineToMove].swapDepths(this[goal].getDepth());//makes sure the moving line is placed on top
                this[lineToMove].animateDots(this[goal]);
                //this is simply a tween that does nothing but count to 4, and when finished it deletes the setInterval used above
                var deleteInterval:Tween = new Tween(this, _x, None.easeNone, _x, _x, 4, true);
                deleteInterval.onMotionFinished = function(){
                        clearInterval(intervalID);
                        trace("interval deleted");
                }
        }
        
        //Used only when the graph units / limits are updated.
        //uses the dataSets array to get the names of each dataSet, then redraws them to the new units
        public function redrawDataSets():Void {
                var movieName:String;
                var lineName:String;
                var color:Object;
                var coordinates:Array = new Array(); //will store the points from a line so they can be redrawn using the add data point functions
                
                //cycles through all data sets in this graph
                for(var i = 0; i < dataSets.length; i++){
                        lineName = dataSets[i];
                        color = this[lineName].color;
                        //stores all the data points x and y coordinates in this dataSet so they can be re-added in a moment
                        this[lineName].reset();
                        coordinates = new Array();
                        for(var j = 0; !this[lineName].atEnd(); j+=2){
                                coordinates[j] = this[lineName].getDataPoint().xcoord;
                                coordinates[j+1] = this[lineName].getDataPoint().ycoord;
                                this[lineName].advance();
                        }
                        
                        addLine(lineName, color);
                        
                        //now re-add all stored data points
                        for(var j = 0; j < coordinates.length; j+=2){
                                addDataPoint(lineName, coordinates[j], coordinates[j+1])
                        }
                        
                        this[lineName].drawPoints();
                        if(this[lineName].pointCount > 1)
                        this[lineName].drawLines();
                }
                
        }
        
        
        //---------------------------------Getter and Setter functions-------------------------------------
        
        //----------Setter functions--------------
        public function set xGridLines(value:Boolean):Void {
                xGraphGridLines = value;
                drawGridLines();
        }
        
        public function set yGridLines(value:Boolean):Void {
                yGraphGridLines = value;
                drawGridLines();
        }
        
        public function set xAxisTicks(value:Boolean):Void {
                this.xAxisDashes = value;
                drawXAxisDashes();
        }
        
        public function set yAxisTicks(value:Boolean):Void {
                this.yAxisDashes = value;
                drawYAxisDashes();
        }
        
        public function set dotStyle(value:String):Void {
                this.dotColor = value;
        }
        
        public function set lineStyle(value:String):Void {
                this.lineColor = value;
        }
        
        public function set backgroundType(value:String):Void {
                this.graphBackgroundType = value;
        }
        
        //updates and redraws the x axis with the new xAxisMax and increment values
        public function set XMax(value:Number):Void {
                this.xAxisMax = value;
                xUnit = int(((graphWidth - xMargin) / xAxisMax)*decimalRound)/decimalRound;
                xAxisIncrementPixels = int((xUnit * xAxisIncrement)*decimalRound)/decimalRound;
                
                drawXAxisNumbers();
                drawGridLines();
                drawXAxisDashes();
        }
        
        //updates and redraws the y axis with the new xAxisMax and increment values
        public function set YMax(value:Number):Void {
                this.yAxisMax = value;
                yUnit = int(((graphHeight - yMargin) / yAxisMax)*decimalRound)/decimalRound;
                yAxisIncrementPixels = int((yUnit * yAxisIncrement)*decimalRound)/decimalRound;
                
                drawYAxisNumbers();
                drawGridLines();
                drawYAxisDashes();
        }
        
        //updates and redraws the x axis at the specified increment
        public function set XIncrement(value:Number):Void {
                xAxisIncrement = value;
                xUnit = int(((graphWidth - xMargin) / xAxisMax)*decimalRound)/decimalRound;
                xAxisIncrementPixels = int((xUnit * xAxisIncrement)*decimalRound)/decimalRound;
                
                drawXAxisNumbers();
                drawGridLines();
                drawXAxisDashes();
        }
        
        //updates and redraws the y axis at the specified increment
        public function set YIncrement(value:Number):Void {
                yAxisIncrement = value;
                yUnit = int(((graphHeight - yMargin) / yAxisMax)*decimalRound)/decimalRound;
                yAxisIncrementPixels = int((yUnit * yAxisIncrement)*decimalRound)/decimalRound;
                
                drawYAxisNumbers();
                drawGridLines();
                drawYAxisDashes();
                
        }
        
        
        //----------Getter functions--------------
        public function get xGridLines():Boolean {
                return xGraphGridLines;
        }
        
        public function get yGridLines():Boolean {
                return yGraphGridLines;
        }
        
        public function get xAxisTicks():Boolean {
                return xAxisDashes;
        }
        
        public function get yAxisTicks():Boolean {
                return yAxisDashes;
        }
        
        public function get backgroundType():String {
                return graphBackgroundType;
        }
        
        public function get dotStyle():String {
                return dotColor;
        }
        
        public function get lineStyle():String {
                return lineColor;
        }
        
        public function get XMax():Number {
                return xAxisMax;
        }
        
        public function get YMax():Number {
                return yAxisMax;
        }
        
        public function get XIncrement():Number {
                return xAxisIncrement;
        }
        
        public function get YIncrement():Number {
                return yAxisIncrement;
        }
        
        
        
        
        
}