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