[moodle] / contrib / plugins / grade / report / visual / flare_visualization / flare_visualization.as Repository:

View of /contrib/plugins/grade/report/visual/flare_visualization/flare_visualization.as

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.3 - (download) (annotate)
Thu Jul 24 15:31:44 2008 WST (16 months ago) by dservos
Branch: MAIN
Changes since 1.2: +383 -111 lines
CONTRIB-497
*Added new visualization, Grade Distribution
*Fixed some minor bugs
*Made abstract visualization class for creating visualizations by making classes witch extend it.
*Made visual_settings.php witch takes a visualization class and truns it in to XML witch flex can read in.
*Made flex visualization application read in XML formated settings as well as tab formated data from moodle and combind them to make a custom visualization.
*Made flex visualization application read and use langue strings from moodle.
*Added printer firendly tab

TODO:
*Add more visualizations
*Refactor some of the flex/actionscript code
*More douctenation
*More UI functions for the flex application
///////////////////////////////////////////////////////////////////////////
//                                                                       //
// NOTICE OF COPYRIGHT                                                   //
//                                                                       //
// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
//          http://moodle.org                                            //
//                                                                       //
// Copyright (C) 1999 onwards  Martin Dougiamas  http://moodle.com       //
//                                                                       //
// This program is free software; you can redistribute it and/or modify  //
// it under the terms of the GNU General Public License as published by  //
// the Free Software Foundation; either version 2 of the License, or     //
// (at your option) any later version.                                   //
//                                                                       //
// This program is distributed in the hope that it will be useful,       //
// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
// GNU General Public License for more details:                          //
//                                                                       //
//          http://www.gnu.org/copyleft/gpl.html                         //
//                                                                       //
///////////////////////////////////////////////////////////////////////////
  
 /**
 * This is the flex with flare based visualizer for the Moodle 2.x visual
 * grade book plug-in. This should load the grade book data for a given
 * visualization from the report/visual plug-in based on a set of flashvars
 * passed to it from Moodle and display a visual repersenation.
 */
package {
	//Flare imports
    import flare.animate.Transitioner;
    import flare.data.DataSet;
    import flare.data.DataSource;
    import flare.display.TextSprite;
    import flare.vis.Visualization;
    import flare.vis.controls.HoverControl;
    import flare.vis.data.Data;
    import flare.vis.data.DataSprite;
    import flare.vis.legend.Legend;
    import flare.vis.legend.LegendItem;
    import flare.vis.operator.encoder.ColorEncoder;
    import flare.vis.operator.encoder.Encoder;
    import flare.vis.operator.encoder.ShapeEncoder;
    import flare.vis.operator.encoder.SizeEncoder;
    import flare.vis.operator.layout.AxisLayout;
    import flare.vis.operator.layout.CircleLayout;
    import flare.vis.operator.layout.DendrogramLayout;
    import flare.vis.operator.layout.ForceDirectedLayout;
    import flare.vis.operator.layout.IndentedTreeLayout;
    import flare.vis.operator.layout.Layout;
    import flare.vis.operator.layout.NodeLinkTreeLayout;
    import flare.vis.operator.layout.PieLayout;
    import flare.vis.operator.layout.RadialTreeLayout;
    import flare.vis.operator.layout.RandomLayout;
    import flare.vis.operator.layout.StackedAreaLayout;
    import flare.vis.operator.layout.TreeMapLayout;
    import flare.vis.scale.Scale;
    import flare.vis.util.Filters;
    
    import flash.display.DisplayObject;
    import flash.display.DisplayObjectContainer;
    import flash.display.Sprite;
    import flash.errors.IOError;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.MouseEvent;
    import flash.filters.GlowFilter;
    import flash.geom.Rectangle;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.text.TextField;
    import flash.text.TextFormat;

    [SWF(width="800", height="600", backgroundColor="#ffffff", frameRate="30")]
    /**
    * Main class for handling grade book data and greatating a visualization.
    */
    public class flare_visualization extends Sprite
    {   
    	/**
    	 * The visualization object to be used in creating the visualization.
    	 */
    	private var vis:Visualization;
    	
    	/**
    	 * A refernce to the currently displayed dialog box. If null no dialog
    	 * box is currently being displayed.
    	 */
    	private var lastBox:Sprite = null;
    	
    	/**
    	 * A refernce to the data sprite witch contains the data for witch the 
    	 * currently displayed dialog box is based on and a child of.
    	 */
    	private var lastBoxData:DataSprite = null;
    	
    	/**
    	 * A container for the legends witch will be displayed on the righ hand
    	 * side.
    	 */
    	private var legends:Sprite;
    	
    	/**
    	 * The hover control for the dialog box.
    	 */
    	private var boxhc:HoverControl = new HoverControl();
    	
    	private var settings:XML = new XML();
    	
    	private var dataURL:String;
    	
    	private var settingsURL:String;
    	
    	private var loadingMessage:TextSprite;
    	
    	/**
    	 * The constucter for the flare_visualization class.
    	 * Calls on harvest_data and sets up the varibles from the flashvars.
    	 */	
        public function flare_visualization()
        {
        	loadingMessage = new TextSprite("Loading....", new TextFormat("monospace", 20, 0x0000FF, true));
        	addChild(loadingMessage);
        	
        	// Call harvest_data, loading needed visualization data from moodle.
        	// The Moodle wwwroot, course id, users sessionid, users session cookie 
        	// and session test data are needed to get the data from moodle are 
        	// loaded threw flashvars.
        	loaderInfo.addEventListener(Event.COMPLETE, function(evt:Event):void {
        		dataURL = loaderInfo.parameters['wwwroot'] + '/grade/report/visual/data.php?id=' + escape(loaderInfo.parameters['courseid']) + '&sessioncookie=' +  escape(loaderInfo.parameters['sessioncookie']) + '&sessionid=' +  escape(loaderInfo.parameters['sessionid']) + '&sessiontest=' + escape(loaderInfo.parameters['sessiontest']) + '&visid=' + escape(loaderInfo.parameters['visid']);
        		settingsURL = loaderInfo.parameters['wwwroot'] + '/grade/report/visual/visual_settings.php?id=' + escape(loaderInfo.parameters['courseid']) + '&visid=' + escape(loaderInfo.parameters['visid']);
        	
        		//dataURL = 'http://localhost/moodle/grade/report/visual/data.php?id=3&sessioncookie=&sessionid=ec7b77fa297d0454fa45367b42761d07&sessiontest=iqw4nC9hc7&visid=grade_distribution';
        		//settingsURL = 'http://localhost/moodle/grade/report/visual/visual_settings.php?id=3&visid=grade_distribution';
        	
        		harvest_data();
        	});
        }
        
        /**
        * Harvests the data from Moodle and calls on buildVis to build the
        * visualization once the data has been loaded.
        * TODO: Add a loading bar and more feed back about the loading process.
        * @param url The url from witch to load the tab formated data for the visualization.
        */
        public function harvest_data():void
        {
        		loadingMessage.text ="Loading Settings....";
        		loadingMessage.x = loaderInfo.width/2 - loadingMessage.width/2;
        		loadingMessage.y = loaderInfo.height/2 - loadingMessage.height/2;
        		 
        		
        		try{
        			var ds:DataSource = new DataSource(dataURL, "tab");
        			var settingsRequest:URLRequest = new URLRequest(settingsURL);
   					var settingsLoader:URLLoader = new URLLoader(settingsRequest);
        	    
        	  	  settingsLoader.addEventListener(IOErrorEvent.IO_ERROR, function(evt:IOErrorEvent):void {
        	  	  	error("Loading", evt.text);	
        	  	  });
        	    
        	  	  settingsLoader.addEventListener(Event.COMPLETE, function(evt:Event):void {
        	  	  	try{
        	  	  		settings = XML(settingsLoader.data);	
        	  	  		loadingMessage.text = "Loading Data....";
        	    
        	   		 	var dataLoader:URLLoader = ds.load();
        	    
        	   	 		dataLoader.addEventListener(IOErrorEvent.IO_ERROR, function(evt:IOErrorEvent):void {
        	    			error("Loading", evt.text);
        	    		});
        	    
        	    		dataLoader.addEventListener(Event.COMPLETE, function(evt:Event):void {
        	        		removeChild(addChild(loadingMessage));
        	        		var data:DataSet = dataLoader.data as DataSet;
        	        		buildVis(Data.fromDataSet(data));
        	    		});
        	  	  	} catch(e:Error) {
        	  	  		error("", e.message);
        	  	  	}
        		  });
        		} catch(e:IOError) {
        			error("IO", e.message);
        		} catch(e:Error) {
        			error("", e.message) 				
        		}
		}
        
        private function error(type:String = "", text:String = ""):void {
        	trace(type + " Error: " + text);
        	
        	var textfield:TextField = new TextSprite(type + " Error: " + text, new TextFormat("monospace", 12, 0xFF0000, true)).textField;
        	textfield.wordWrap = true;
        	
        	addChild(textfield);	
        }
     
     	/**
     	 * Find the max width between a container and all of it's decendence
     	 * This dose not find the width of a container but the greatest width
     	 * of an invdual component in it's decenedences.
     	 * @param d The display container to find the max width of.
     	 * @return the max width value of the display objects.
     	 */ 
     	private function getMaxWidth(d:DisplayObjectContainer):int {
     		var max:int = d.width;
     		  
            for(var k:uint = 0; k < d.numChildren; k++ ) {  
            	var width:int = 0;
            	
                if(d.getChildAt(k) is DisplayObjectContainer) {
                	width = getMaxWidth(DisplayObjectContainer(d.getChildAt(k)));  
                } else {
                	width = d.getChildAt(k).width;
                }
  				
  				if(width > max) {
  					max = width;
  				}
            }
            
            return max;
        }
     
     	/**
     	 * Simple function to retrun the greatest of two ints.
     	 * @param num1 the first number to test
     	 * @param num2 the second number to test
     	 * @return the largest value between num1 and num2.
     	 */
     	private function max(num1:int, num2:int):int {
     		if(num1 > num2) {
     			return num1;
     		} else {
     			return num2;
     		}
     	}
     
     	/**
     	 * Find the max height between a container and all of it's decendence
     	 * This dose not find the width of a container but the greatest height
     	 * of an invdual component in it's decenedences.
     	 * @param d The display container to find the max height of.
     	 * @return the max height value of the display objects.
     	 */ 
     	private function getMaxHeight(d:DisplayObjectContainer):int {
     		var max:int = d.height;
     		  
            for(var k:uint = 0; k < d.numChildren; k++ ) {  
            	var height:int = 0;
            	
                if(d.getChildAt(k) is DisplayObjectContainer) {
                	height = getMaxHeight(DisplayObjectContainer(d.getChildAt(k)));  
                } else {
                	height = d.getChildAt(k).height;
                }
  				
  				if(height > max) {
  					max = height;
  				}
            }
            
            return max;
        }  
     
     	private function nullify(o:*):* {
     		if(isnull(o)) {
     			return null;
     		} else {
     			return o;
     		}
     	}
     	
     	private function isnull(o:*):Boolean {
     		if(o == null || o.length == 0 || String(o).length == 0) {
     			return true;
     		} else {
     			return false;
     		}
     	}
     	
     	private function booleanify(o:*):* {
     		var ob:Object = nullify(o);
     		
     		if(String(ob).toLocaleLowerCase() == "false") {
     			return false;
     		} else if (String(ob).toLocaleLowerCase() == "true") {
     			return true;
     		}
     		
     		return ob;	
     	}
     	
     	private function passSettings(theClass:Class, XMLSettings:XMLList, ... args):* {
     		var params:Array = new Array();
     		
     		for each(var arg:* in args) {
     			params.push(arg);
     		}
     		
     		for each(var param:* in XMLSettings) {
     			var cleanParam:* = booleanify(param);
     			if(cleanParam != null) {
     				params.push(cleanParam);
     			} 
     		}
     		
     		switch(params.length) {
     			case 1: return new theClass(params[0]);
     			case 2: return new theClass(params[0], params[1]);
     			case 3: return new theClass(params[0], params[1], params[2]);
     			case 4: return new theClass(params[0], params[1], params[2], params[3]);
     			case 5: return new theClass(params[0], params[1], params[2], params[3], params[4]);
     			case 6: return new theClass(params[0], params[1], params[2], params[3], params[4], params[5]);
     			case 7: return new theClass(params[0], params[1], params[2], params[3], params[4], params[5], params[6]);
     			case 8: return new theClass(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]);
     			case 9: return new theClass(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8]);
     			case 10: return new theClass(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8], params[9]);
     			default: return new theClass();
     		}
     	}
     
     	private function toStringArray(xmllist:XMLList):Object {
     		if(xmllist.length() > 1) {
     			var array:Array = new Array;
     			
     			for each(var element:XML in xmllist) {
     				array.push(String(element));
     			}
     			
     			return array;
     		} else {
     			return String(xmllist);
     		}
     	}
     	
     	private function toNumberArray(xmllist:XMLList):Object {
     		if(xmllist.length() > 1) {
     			var array:Array = new Array;
     			
     			for each(var element:XML in xmllist) {
     				array.push(Number(element));
     			}
     			
     			return array;
     		} else {
     			return Number(xmllist);
     		}
     	}
     
     	/**
     	 * Builds the visualization based on the loaded data.
     	 * Also sets up the legends, buttons and controls.
     	 * @param data The data that was loaded in from moodle.
     	 */
     	private function buildVis(data:Data):void
        {
            if(!isnull(settings.edge)) {
            	for each(var edge:XML in settings.edge) {
            		data.createEdges(toStringArray(edge.sortby), toStringArray(edge.groupby));
            	}
            }
            
            vis = new Visualization(data);
            legends = new Sprite();
            
            // Set the functions to be called when a dialog box is hovered over.
            boxhc.onRollOver = boxRollOver;
			boxhc.onRollOut = boxRollOut;
            
			// Set up the layout
			var layout:Layout = new AxisLayout(settings.layout.xaxis.field, settings.layout.yaxis.field);
			switch(int(settings.layout.type)) {
				case 1: layout = passSettings(AxisLayout, settings.layout.setting, settings.layout.xaxis.field, settings.layout.yaxis.field);  
						break;
				case 2:	layout = passSettings(CircleLayout, settings.layout.setting);
						break;
				case 3: layout = passSettings(DendrogramLayout, settings.layout.setting);
						break;
				case 4: layout = passSettings(ForceDirectedLayout, settings.layout.setting);
						break;
				case 5: layout = passSettings(IndentedTreeLayout, settings.layout.setting);
						break;
				case 6: layout = passSettings(NodeLinkTreeLayout, settings.layout.setting);
						break;
				case 7: layout = passSettings(PieLayout, settings.layout.setting);
						break;
				case 8: layout = passSettings(RadialTreeLayout, settings.layout.setting);
						break;
				case 9: layout = new RandomLayout();
						break;
				case 10: layout = passSettings(StackedAreaLayout, settings.layout.setting);
						break;
				case 11: layout = new TreeMapLayout();
						break;
				default: layout = passSettings(AxisLayout, settings.layout.setting, settings.layout.xaxis.field, settings.layout.yaxis.field);
						break;
			}
			vis.operators.add(layout);
			
			// Set up the encoders
			var encoders:Array = new Array(settings.encoder.length);
			for each(var encoder:XML in settings.encoder) {
				var e:Encoder;
				
				switch(int(encoder.type)) {
					case 1: e = passSettings(ColorEncoder, encoder.setting, encoder.datafield);
							break;
					case 2: e = passSettings(ShapeEncoder, encoder.setting, encoder.datafield);
							break;
					case 3: e = passSettings(SizeEncoder, encoder.setting, encoder.datafield);
							break;
					default: e = passSettings(ColorEncoder, encoder.setting, encoder.datafield);
							break;
				}
				
				encoders[encoder.id] = e;
				vis.operators.add(e);
			}
            
            // Set up the layout of the axes.
            vis.xyAxes.xAxis.horizontalAnchor = TextSprite.LEFT; 
			vis.xyAxes.xAxis.verticalAnchor = TextSprite.MIDDLE; 
			vis.xyAxes.xAxis.labelAngle = Math.PI / 2;
			vis.xyAxes.xAxis.fixLabelOverlap = false;
			vis.xyAxes.yAxis.fixLabelOverlap = false;
			
			if(!isnull(settings.layout.yaxis.labelformat)) {
				vis.xyAxes.yAxis.labelFormat = settings.layout.yaxis.labelformat;
			} else {
				vis.xyAxes.yAxis.labelFormat = "0";
			}
			
			if(!isnull(settings.layout.xaxis.labelformat)) {
				vis.xyAxes.xAxis.labelFormat = settings.layout.xaxis.labelformat;
			} else {
				vis.xyAxes.xAxis.labelFormat = "0";
			}
			
			if(!isnull(settings.layout.xaxis.min)) {
				vis.xyAxes.xAxis.axisScale.min = settings.layout.xaxis.min;
				vis.xyAxes.xAxis.axisScale.flush = true;
			}
			
			if(!isnull(settings.layout.xaxis.max)) {
				vis.xyAxes.xAxis.axisScale.max = settings.layout.xaxis.max;
				vis.xyAxes.xAxis.axisScale.flush = true;
			}
			
			if(!isnull(settings.layout.yaxis.min)) {
				vis.xyAxes.yAxis.axisScale.min = settings.layout.yaxis.min;
			}
			
			if(!isnull(settings.layout.yaxis.max)) {
				vis.xyAxes.yAxis.axisScale.max = settings.layout.yaxis.max;
			}
			
			if(!isnull(settings.layout.yaxis.yoffset)) {
				vis.xyAxes.yAxis.labelOffsetY = settings.layout.yaxis.yoffset;
			}
			
			if(!isnull(settings.layout.yaxis.xoffset)) {
				vis.xyAxes.yAxis.labelOffsetX = settings.layout.yaxis.xoffset;
			}
			
			if(!isnull(settings.layout.xaxis.yoffset)) {
				vis.xyAxes.xAxis.labelOffsetY = settings.layout.xaxis.yoffset;
			}
			
			if(!isnull(settings.layout.xaxis.xoffset)) {
				vis.xyAxes.xAxis.labelOffsetX = settings.layout.xaxis.xoffset;
			}
			
			
            // Update the visualization so the widths and other values are correct.
            vis.update();
			
			// Initalize the X and Y axis labels and the visualizations title.
			var labelX:TextSprite = new TextSprite(settings.labels.xaxis, new TextFormat(settings.style.text.font, settings.style.text.size)); 
			var labelY:TextSprite = new TextSprite(settings.labels.yaxis, new TextFormat(settings.style.text.font, settings.style.text.size));
			var title:TextSprite = new TextSprite(settings.labels.title, new TextFormat(settings.style.text.font, int(settings.style.text.size) + 5));
			
			// Find the largest width out of the X axis labels so it can used for positing sprites.
           	var xLabelsHeight:int = getMaxHeight(vis.xyAxes.xAxis.labels);
            var yLabelsWidth:int = getMaxWidth(vis.xyAxes.yAxis.labels);
            
			// Position the visualization.
			vis.y = title.height + 10;
            vis.x = labelY.height + -vis.xyAxes.yAxis.labelOffsetX + yLabelsWidth;
			
			// Set up the legends.
			var nextLegendY:int = 0;
			for each(var legend:XML in settings.legend) {
				var en:Encoder = encoders[legend.encoderid];
				var l:Legend;
				
				switch(int(settings.encoder.(id == int(legend.encoderid)).type)) {
					case 1: l = new Legend(en.source, en.scale, ColorEncoder(en).colors);
							break;
					case 2: l = new Legend(en.source, en.scale, null, ShapeEncoder(en).shapes);
							break;
					case 3: l = new Legend(en.source, en.scale, null, null, SizeEncoder(en).sizes);
							break;
					default: l = new Legend(en.source, en.scale, ColorEncoder(en).colors);
							break;
				} 
				
				l.x = 0;
				l.y = nextLegendY;
				nextLegendY += l.height;
				
				l.items.addEventListener(MouseEvent.CLICK, legendClick);
				
				var lhc:HoverControl = new HoverControl(l.items);
            	lhc.onRollOver = legendRollOver;
				lhc.onRollOut = legendRollOut;
				
				legends.addChild(l);
			}
			
            // Set the bounds of the visualization based on the hieght and width of the flash application,
            // and the other components so the visualization is takes up the unused space.
            vis.bounds = new Rectangle(0, 0, loaderInfo.width - (legends.width + 15 + vis.x), loaderInfo.height - (vis.y + xLabelsHeight + labelX.height + vis.xyAxes.xAxis.labelOffsetY)); 
            
            // Add the visualization to the main sprite.
            addChild(vis);
            
            // Set up the properitys of the data sprites and add a eventlistener to check for
            // clicks on them.
            vis.data.nodes.visit(function(d:DataSprite):void {
            	if(!isnull(settings.style.nodeshape)) {
            		d.shape = settings.style.nodeshape;
            	}
				d.fillColor = 0x018888ff;
				d.fillAlpha = 0.2;
				d.lineWidth = 2;
				d.addEventListener(MouseEvent.CLICK, mouseClicked);
				
			});
			
			vis.data.edges.visit(function(d:DataSprite):void {
				if(!isnull(settings.style.edgeshape)) {
					d.shape = settings.style.edgeshape;
				}
				d.lineWidth = 2;
				d.fillAlpha = 1;
			});
             
            // Position the legends.
            legends.x = vis.bounds.width + 10;
            
            // Position and add the labels and title to the axes.
			labelX.x = vis.bounds.width/2 - labelX.width/2;
			labelX.y = vis.bounds.height + vis.xyAxes.xAxis.labelOffsetY + xLabelsHeight;
            vis.xyAxes.xAxis.addChild(labelX);
            
			labelY.rotation = -90;
			labelY.x = -vis.x;
			labelY.y = (vis.bounds.height/2) + (labelY.height/2);
			vis.xyAxes.yAxis.addChild(labelY);
			
			title.x = vis.bounds.width/2 - title.width/2;
			title.y = -vis.y;
			vis.xyAxes.addChild(title);
            
      		// Add the legeneds container to the visualization.
            vis.addChild(legends);
            
            // Set up the hovercontrol for the marks on the chart
            var hc:HoverControl = new HoverControl(vis, Filters.isDataSprite);
            hc.onRollOver = rollOver;
			hc.onRollOut = rollOut;
			
			// Set up the buttons and a container for them.
			var controls:Sprite = new Sprite();
			var bInvert:Button = new Button(settings.lang.invertaxes, settings.style.button);
			var bHideAxis:Button = new Button(settings.lang.hide + " " + settings.lang.axes, settings.style.button);
			var bHideXLabel:Button = new Button(settings.lang.hide + " " + settings.lang.xlabels, settings.style.button);
			var bHideYLabel:Button = new Button(settings.lang.hide + " " + settings.lang.ylabels, settings.style.button);
			
			var hideXLabelTransitioner:Transitioner = new Transitioner(2);
			hideXLabelTransitioner.onEnd = updateMarkVisiblity;
			hideXLabelTransitioner.onStart = updateMarkVisiblity;
			
			bHideXLabel.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void {
				if(!hideXLabelTransitioner.running) {
					hideXLabelTransitioner.reset();
					
					if(bHideXLabel.text == settings.lang.show + " " +  settings.lang.xlabels) {
						bHideXLabel.text = settings.lang.hide + " " + settings.lang.xlabels;
						vis.xyAxes.xAxis.showLabels = true;
						vis.bounds = new Rectangle(0, 0, loaderInfo.width - (legends.width + 15 + vis.x), loaderInfo.height - (vis.y + xLabelsHeight + labelX.height + vis.xyAxes.xAxis.labelOffsetY));
					} else {
						bHideXLabel.text = settings.lang.show + " " +  settings.lang.xlabels;
						vis.xyAxes.xAxis.showLabels = false;
						vis.bounds = new Rectangle(0, 0, loaderInfo.width - (legends.width + 15 + vis.x), loaderInfo.height - (vis.y + labelX.height));
					}
					
					hideXLabelTransitioner.$(labelY).x = -vis.x;
					hideXLabelTransitioner.$(labelY).y = vis.bounds.height/2 - labelY.height/2;
				
					vis.update(hideXLabelTransitioner).play();
				}
			});
			
			var hideYLabelTransitioner:Transitioner = new Transitioner(2);
			hideYLabelTransitioner.onEnd = updateMarkVisiblity;
			hideYLabelTransitioner.onStart = updateMarkVisiblity;
			
			bHideYLabel.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void {
				if(!hideYLabelTransitioner.running) {
					var t:Transitioner = new Transitioner(2);
					var newX:int;
					
					hideYLabelTransitioner.reset();
					
					if(bHideYLabel.text == settings.lang.show + " " +  settings.lang.ylabels) {
						bHideYLabel.text = settings.lang.hide + " " +  settings.lang.ylabels;
						vis.xyAxes.yAxis.showLabels = true;
						newX = labelY.width + -vis.xyAxes.yAxis.labelOffsetX + yLabelsWidth;
					} else {
						bHideYLabel.text = settings.lang.show + " " +  settings.lang.ylabels;
						vis.xyAxes.yAxis.showLabels = false;
						newX = labelY.width;
					}
				
					t.$(vis).x = newX;
					vis.bounds = new Rectangle(0, 0, loaderInfo.width - (legends.width + 15 + newX), loaderInfo.height - (vis.y + xLabelsHeight + labelX.height + vis.xyAxes.xAxis.labelOffsetY));
					
					// Reposition the labels and title. 
					t.$(title).x = vis.bounds.width/2 - title.width/2;
					t.$(labelX).x = vis.bounds.width/2 - labelX.width/2;
					t.$(labelX).y = vis.bounds.height + vis.xyAxes.xAxis.labelOffsetY + xLabelsHeight;
					t.$(labelY).x = -newX;
					t.$(labelY).y = vis.bounds.height/2 - labelY.height/2;
				
					// Keep the legends in there place.
					t.$(legends).x = vis.bounds.width + 10;
					
					t.play();
					vis.update(hideYLabelTransitioner).play();
				}
			});
			
			// Set up the transitioner to be used when inverting the axes
			var updateTransitioner:Transitioner = new Transitioner(2);
			updateTransitioner.onEnd = function():void {
				updateMarkVisiblity();
				vis.xyAxes.xAxis.labels.visible = true;
				vis.xyAxes.yAxis.labels.visible = true;
   			};
            updateTransitioner.onStart = function():void {
            	updateMarkVisiblity();
            	vis.xyAxes.xAxis.labels.visible = false;
				vis.xyAxes.yAxis.labels.visible = false;
            }
			
			// The function to invert the axes.
			bInvert.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void {
				// If we are not allready in the process of inverting the axes.
				if(!updateTransitioner.running) {
					var t:Transitioner = new Transitioner(2);
					var tempText:String = labelX.text;
					var tempOffset:int = vis.xyAxes.xAxis.labelOffsetX;
					var tempWidth:uint = vis.bounds.width;
					var tempLabelFormat:String = vis.xyAxes.xAxis.labelFormat;
					var tempLabels:int = xLabelsHeight;
					var tempScale:Scale = vis.xyAxes.xAxis.axisScale;
					var tempLabelOffsetY:Number = vis.xyAxes.xAxis.labelOffsetY;
					var tempLabelOffsetX:Number = vis.xyAxes.xAxis.labelOffsetX;
					var currentXLabelsHeight:int = getMaxWidth(vis.xyAxes.yAxis.labels);
					
					var tempShowLabels:Boolean = vis.xyAxes.xAxis.showLabels;
				
					// Rest the transitioner for a clean transition.
					updateTransitioner.reset();		
					
					vis.xyAxes.xAxis.axisScale = vis.xyAxes.yAxis.axisScale;
					vis.xyAxes.yAxis.axisScale = tempScale;
					vis.xyAxes.yAxis.axisScale.flush = true;
					vis.xyAxes.xAxis.axisScale.flush = true
					
					// Flip the axis feilds.
					if(settings.layout.type == 1) {
						AxisLayout(layout).xField = settings.layout.yaxis.field;
						AxisLayout(layout).yField = settings.layout.xaxis.field;
						settings.layout.xaxis.field = AxisLayout(layout).xField;
						settings.layout.yaxis.field = AxisLayout(layout).yField;
					
						var tempStack:Boolean = AxisLayout(layout).xStacked;
						AxisLayout(layout).xStacked = AxisLayout(layout).yStacked;
						AxisLayout(layout).yStacked = tempStack;
					}
					
					vis.xyAxes.xAxis.labelFormat = vis.xyAxes.yAxis.labelFormat;
					vis.xyAxes.yAxis.labelFormat = tempLabelFormat;
					
					
					vis.xyAxes.xAxis.labelOffsetX = vis.xyAxes.yAxis.labelOffsetY * -1;
					vis.xyAxes.yAxis.labelOffsetY = tempLabelOffsetX * -1;
					vis.xyAxes.xAxis.labelOffsetY = vis.xyAxes.yAxis.labelOffsetX * -1;
					vis.xyAxes.yAxis.labelOffsetX = tempLabelOffsetY * -1;
					
					
					xLabelsHeight = yLabelsWidth;
					yLabelsWidth = tempLabels;
				
				
					vis.xyAxes.xAxis.showLabels = vis.xyAxes.yAxis.showLabels;
					vis.xyAxes.yAxis.showLabels = tempShowLabels;
				
					if(vis.xyAxes.yAxis.showLabels) {
						bHideYLabel.text = settings.lang.hide + " " +  settings.lang.ylabels;
					} else {
						bHideYLabel.text = settings.lang.show + " " +  settings.lang.ylabels;
					}
					
					if(vis.xyAxes.xAxis.showLabels) {
						bHideXLabel.text = settings.lang.hide + " " +  settings.lang.xlabels;
					} else {
						bHideXLabel.text = settings.lang.show + " " +  settings.lang.xlabels;
					}
					
					// Flip the labels
					labelX.text = labelY.text;
					labelY.text = tempText;
				
					// Find the new X value for the visualization.
					var newX:int = labelY.width + vis.xyAxes.xAxis.labelOffsetY + getMaxHeight(vis.xyAxes.xAxis.labels);
				
					// Reposition and set the bounds of the visualization.
					t.$(vis).x = newX;
					vis.bounds = new Rectangle(0, 0, loaderInfo.width - (legends.width + 15 + newX), loaderInfo.height - (vis.y + currentXLabelsHeight + labelX.height + vis.xyAxes.xAxis.labelOffsetY));
				
					// Reposition the labels and title. 
					t.$(title).x = vis.bounds.width/2 - title.width/2;
					t.$(labelX).x = vis.bounds.width/2 - labelX.width/2;
					t.$(labelX).y = vis.bounds.height + vis.xyAxes.xAxis.labelOffsetY + currentXLabelsHeight;
					t.$(labelY).x = -newX;
					t.$(labelY).y = vis.bounds.height/2 + labelY.height/2;
				
					// Keep the legends in there place.
					t.$(legends).x = vis.bounds.width + 10;
				
					//Play the transition.
            		t.play();
            		vis.update(updateTransitioner).play();
    			}
			});
			
			// Set up the transitioner for the hide axes button.
			var hideAxisTrans:Transitioner = new Transitioner(1);
			
			// Function for hidding the axes.
			bHideAxis.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void {
				// If we are not allready in the process of hidding the axes
				if(!hideAxisTrans.running) {
					// Reset the transitoner for a clean transiton.
					hideAxisTrans.reset();
					
					// Hide or show the axes.
					if(bHideAxis.text == settings.lang.show + " " + settings.lang.axes) {
						hideAxisTrans.$(bHideAxis).text = settings.lang.hide + " " + settings.lang.axes;
						layout.showAxes(hideAxisTrans).play();
					} else {
						hideAxisTrans.$(bHideAxis).text = settings.lang.show + " " + settings.lang.axes;
						layout.hideAxes(hideAxisTrans).play();
					}
				}
			});
			
			// Position the buttons inside there container.
			bHideXLabel.x = 0;
			bHideXLabel.y = 0;
			
			bHideAxis.x = legends.width - bHideAxis.width - 5;
			bHideAxis.y = bHideXLabel.y;
			
			bInvert.x = legends.width - bInvert.width - 5;
			bInvert.y = bHideXLabel.y + bHideXLabel.height + 2;
			
			bHideYLabel.x = 0;
			bHideYLabel.y = bHideXLabel.y + bHideXLabel.height + 2;
			
			// Poistion the buttons container.
			controls.x = legends.x + vis.x; 
			controls.y = legends.y + legends.height + vis.y + 20; 
			
			// Add the buttons to the container and the container to the main sprite.
			controls.addChild(bInvert);
			controls.addChild(bHideAxis);
			controls.addChild(bHideXLabel);
			controls.addChild(bHideYLabel);
			addChild(controls);
	
			// Set the marks on the chart to the higest deepth.
            vis.setChildIndex(vis.marks, vis.numChildren - 1);
	
			// Update.
			vis.update();
			updateMarkVisiblity();
        }
        
        /**
        * Roll over function witch makes the object 0.5 units bigger and adds a glow filter.
        * @param ob the object witch was rolled over.
        */
        private function rollOver(ob:Object):void {
        	ob.filters = [new GlowFilter(0xFFFF55, 0.8, 6, 6, 10)];
        	ob.size += 0.5;
        }
        
        /**
        * Roll out function witch removes the filters and makes the object 0.5 units smaller.
        * @param ob the object witch was rolled out of.
        */
        private function rollOut(ob:Object):void {
        	ob.filters = null;
        	ob.size -= 0.5;
        }
        
        /**
        * Roll over function for the dialog box.
        * Adds a glow filter to the curently active dialog box.
        * @param ob a child of the dialog box.
        */
        private function boxRollOver(ob:Object):void {
        	if(lastBoxData != null) {
        		lastBoxData.filters = [new GlowFilter(0xFFFF55, 0.8, 6, 6, 10)];
        	}
        }
        
        /**
        * Roll out function for the dialog box.
        * Removes filters on the curently active dialog box.
        * @param ob a child of the dialog box.
        */
        private function boxRollOut(ob:Object):void {
        	if(lastBoxData != null) {
        		lastBoxData.filters = null;
        	}
        }
        
        /**
        * Finds the Legend belonging to the LegendItem passed.
        * TODO: See if this can be replaced by a .parent call.
        * @param item a LegendItem to find the Legend of.
        * @return the Legend that contains the passed LegendItem.
        */
        private function findLegendByItem(item:LegendItem):Legend {
       		for(var i:uint = 0; i < legends.numChildren; i++ ) {
        		if(Legend(legends.getChildAt(i)).items.contains(item)) {
        			return Legend(legends.getChildAt(i));
        		}
        	}
        	
        	return null;
        }
        
        /**
        * Roll over function for legends.
        * Adds a glow filter to the legend's item aswell as all the markers on the chart
        * that are realted to the legend item and incrases there size by 1 unit.
        * @param ob the LegendItem being rolled over.
        */ 
        private function legendRollOver(ob:LegendItem):void {
        	var legend:Legend = findLegendByItem(ob);
        	var dataName:String = legend.dataField.substr(legend.dataField.lastIndexOf('.') + 1);
        	
        	ob.filters = [new GlowFilter(0xFFFF55, 0.8, 6, 6, 10)];
        	
        	vis.data.visit(function(d:DataSprite):void {
				if(d.data.hasOwnProperty(dataName) && ob.value == d.data[dataName]) {
					d.filters = [new GlowFilter(0xFFFF55, 0.8, 6, 6, 10)];
					d.size += 1;
				}
			}, 3, Filters.isDataSprite);
        }
        
        /**
        * Roll out function for legends.
        * Removes filters to the legend's item aswell as all the markers on the chart
        * that are realted to the legend item and decrases there size by 1 unit.
        * @param ob the LegendItem being rolled out of.
        */ 
        private function legendRollOut(ob:LegendItem):void {
        	var legend:Legend = findLegendByItem(ob);
        	var dataName:String = legend.dataField.substr(legend.dataField.lastIndexOf('.') + 1);
        	
        	ob.filters = null;
        	
        	vis.data.visit(function(d:DataSprite):void {
				if(d.data.hasOwnProperty(dataName) && ob.value == d.data[dataName]) {
					d.filters = null;
					d.size -= 1;
				}
			}, 3, Filters.isDataSprite);
        }
        
        /**
        * Creates and returns a dialog box containing information on the passed data sprite.
        * @param data the DataSprite containing the information to display.
        * @returns the Sprite containing the dialog box.
        */
        private function dataDialogBox(data:DataSprite):Sprite {
        	var box:Sprite = new Sprite;
        	
        	var backGround:Sprite = new Sprite;	
        	backGround.graphics.beginFill(parseInt(settings.style.popup.bgcolor, 16), settings.style.popup.alpha);
        	backGround.graphics.lineStyle(settings.style.popup.line.size, parseInt(settings.style.popup.line.color, 16), settings.style.popup.line.alpha);
        	 
        	var text:Sprite = new Sprite;
        	var x:int = 5;
        	var y:int = 0;
        	
        	for(var property:Object in data.data) {
        		var temp:TextSprite = new TextSprite(property.toString() + ": " + data.data[property], new TextFormat(settings.style.popup.text.font, settings.style.popup.text.size, null, true));
        		temp.x = x;
        		temp.y = y;
        		text.addChild(temp);
        		y += temp.height;
        	}
   
        	backGround.graphics.drawRoundRect(0, 0, text.width + 10, text.height, 30, 30);     	
        	
        	box.addChild(backGround);
        	box.addChild(text);
        	
        	return box;
        }
        
        /**
        * Check if a mark on the chart is visible based on the related LegendItems states.
        * @param d the DataSprite to check the visiblility of.
        * @returns true if the mark is visible.
        */
        private function markIsVisible(d:DataSprite):Boolean {
        	var items:Array = getLegendItems(d);
        	
        	for each(var item:LegendItem in items) {
        		if(item.alpha != 1) {
        			return false;
        		}
        	}
        	
        	return true;
        }
        
        /**
        * Gets all LegenedItems realted to a given DataSprite/mark.
        * @params d the DataSprite on the chart.
        * @returns Array of LegendItems that are realted to the given DataSprite.
        */
        private function getLegendItems(d:DataSprite):Array {
        	var items:Array = new Array();
        	
        	for(var i:uint = 0; i < legends.numChildren; i++) {
        		var legend:Legend = Legend(legends.getChildAt(i));

        		for(var k:uint = 0; k < legend.items.numChildren; k++) {
        			var item:LegendItem = LegendItem(legend.items.getChildAt(k));
        			
        			if(d.data[legend.dataField.substr(legend.dataField.lastIndexOf('.') + 1)] == item.value) {
        				items.push(item);
        				break;
        			}
        		}
        	}
        	
        	return items;
        }
        
        /**
        * Sets the visible atrubute of the marks/DataSprites based on the status of the LegendItems realted to it.
        * @param item if set, only updates marks for that LegendItem. Otherwise updates all marks.
        */ 
        private function updateMarkVisiblity(item:LegendItem=null):void {
        	var legend:Legend;
        	var dataName:String;
        	
        	if(item != null) {
        		legend = findLegendByItem(item);
        		dataName = legend.dataField.substr(legend.dataField.lastIndexOf('.') + 1);
        	}
        	
        	vis.data.visit(function(d:DataSprite):void {
				if(item == null || (d.data.hasOwnProperty(dataName) && item.value == d.data[dataName])) {		
					if(markIsVisible(d)) {
						//d.alpha = 1.0;
						d.visible = true;
					} else {
						//d.alpha = 0.0;
						d.visible = false;
					}
				}
			}, 3, Filters.isDataSprite);
        }
        
        /**
        * Function to be called when a LegendItem is clicked.
        * Changes the legendItems alpah value and updates mark visiblity.
        * @param evt the mouse event.
        */
        private function legendClick(evt:MouseEvent):void {
        	var item:LegendItem = LegendItem(evt.target);
        	
        	if(item.alpha == 1) {
        		item.alpha = 0.4;
        	} else {
        		item.alpha = 1.0;
        	}
        	
        	updateMarkVisiblity(item);
        	vis.update();
        }
        
        /**
        * Function called when a click happens on a mark on the chart.
        * Creates and adds a dialog box for that mark/DataSprite when clicked or removes the dialog box if
        * the mark allready has one.
        * @param the mouse event.
        */
        private function mouseClicked(evt:MouseEvent):void {
        	if(DisplayObject(evt.target).parent == vis.marks) {
        		if(lastBox != null && lastBoxData != null) {
        			lastBoxData.removeChild(lastBox);
        			boxhc.detach();
        		}
        		
        		if(evt.target != lastBoxData) {
        			lastBox = dataDialogBox(DataSprite(evt.target));
        			lastBoxData = DataSprite(evt.target);
        			Sprite(evt.target).addChild(lastBox);
    				vis.marks.setChildIndex(Sprite(evt.target), vis.marks.numChildren - 1);
    				boxhc.attach(Sprite(evt.target));
        		} else {
        			lastBoxData = null;
        			lastBox = null;
        		}
        	}
        }
    }
}

Moodle CVS Admin
ViewVC Help
Powered by ViewVC 1.0.7