package { import flare.animate.TransitionEvent; import flare.animate.Transitioner; import flare.display.TextSprite; import flare.query.methods.*; import flare.scale.ScaleType; import flare.scale.TimeScale; import flare.util.Dates; import flare.util.Orientation; import flare.util.Shapes; import flare.util.Sort; import flare.util.Strings; import flare.util.palette.ColorPalette; import flare.vis.Visualization; import flare.vis.controls.ClickControl; import flare.vis.controls.HoverControl; import flare.vis.controls.TooltipControl; import flare.vis.data.Data; import flare.vis.data.DataList; import flare.vis.data.DataSprite; import flare.vis.data.EdgeSprite; import flare.vis.data.NodeSprite; import flare.vis.events.SelectionEvent; import flare.vis.events.TooltipEvent; import flare.vis.operator.OperatorList; import flare.vis.operator.encoder.ColorEncoder; import flare.vis.operator.encoder.PropertyEncoder; import flare.vis.operator.filter.VisibilityFilter; import flare.vis.operator.label.StackedAreaLabeler; import flare.vis.operator.layout.AxisLayout; import flare.widgets.ProgressBar; import flare.widgets.SearchBox; import flash.display.DisplayObject; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.filters.DropShadowFilter; import flash.geom.Rectangle; import flash.text.TextField; import flash.text.TextFieldType; import flash.text.TextFormat; import flash.ui.Keyboard; import lastfmvis.CheaterEvent; import lastfmvis.DataProcessor; import lastfmvis.StackedAreaLayout; import mx.core.Application; import mx.core.UIComponent; [Event(name="VIS_LOADED")] [Event(name="DRILL_DOWN")] public class Last_fm_vis extends UIComponent { public static const VIS_LOADED:String = "vis loaded"; public static const DRILL_DOWN:String = "drilldown"; public static const TAG_TYPE:uint = 1; public static const ARTIST_TYPE:uint = 0; public static const TRACK_TYPE:uint = 2; // Initialized in init() etc... private var dataProcessor:DataProcessor = null; // Used by change min/max date to store removed data points private var rawChartData:Array = []; private var chartData:Data; private var removedData:Object = {}; // Username vars private var usernameEntryFormat:TextFormat = new TextFormat("Trebuchet MS", 25, 0, true, null, null, null, null, "left"); private var _usernameEntry:TextField; private var _username:String = ""; private var usernameLabel:TextSprite = new TextSprite("Username", usernameEntryFormat); // Progress bar private var progressBar:ProgressBar = new ProgressBar(250,25); // Property to base stacks of data on, artist, tag, etc private var filterProperty:uint; //private var FMT:TextFormat = new TextFormat("Helvetica,Arial",16,0,true); public static const FMT:TextFormat = new TextFormat("Arial",16,0,true); // Visualiations private var stackedVisualization:Visualization; private var lineVisualization:Visualization; // The currently viewed visualization private var vis:Visualization; private var _bounds:Rectangle; private var _t:Transitioner; private var _dur:Number = 1.25; // Animation duration private static var _timeRangeDur:Number = 0.5; /** The first week to be displayed on the graph */ private var _startWeek:Date; /** The last week to be displayed on the graph */ private var _endWeek:Date; // Sprite to hold the two visualizations private var visHolder:Sprite; /** The tag that is currently being viewed; for artist view. */ private var currentTag:String; /** The artist that is currently being viewed; for track view. */ private var currentArtist:String; /** Sort object to sort data by */ private var sorter:Sort = new Sort(["+data.sort", "+data.name"]); /** Node currently placed on bottom of Stacked Graph */ private var focusedNode:NodeSprite; // Query variables private var query:Array; private var exact:Boolean; // Exact search requested // Size of component private var totalWidth:uint; private var totalHeight:uint; // Box in right-hand corner which shows tag info private var tagInfoBox:Sprite; public function get dates():Array { return dataProcessor.dates } public static function get timeRange():Number { return _timeRangeDur } public function get usernameEntry():TextField { return _usernameEntry; } public function get username():String { return _username; } public function set usernameY(y:Number):void { _usernameEntry.y = y; usernameLabel.y = _usernameEntry.y - _usernameEntry.height; } public function get usernameY():Number { return _usernameEntry.y; } public function set bounds(rect:Rectangle):void { // Set the visualization bounds // new Rectangle(0,0,visWidth,visHeight); _bounds = rect; // _bounds.y += search.y + search.height + yMargin; lineVisualization.bounds = _bounds; stackedVisualization.bounds = _bounds; trace(_bounds); lineVisualization.update(); stackedVisualization.update(); } public function reBound(time:Number):void { _bounds = new Rectangle(0, 0, this.width - 100, this.height - 100); lineVisualization.bounds = _bounds; stackedVisualization.bounds = _bounds; lineVisualization.update(); stackedVisualization.update(); /* _t = new Transitioner(time); _t.$(lineVisualization).width = this.width; _t.$(lineVisualization).height = this.height; _t.$(stackedVisualization).width = this.width; _t.$(stackedVisualization).height = this.height; _t.play(); */ } /** * Runs the program. */ public function Last_fm_vis(x:uint, y:uint, width:uint = 800, height:uint = 600) { this.x = x; this.y = y; this.width = totalWidth = width; this.height = totalHeight = height; init(); } private function init():void { visHolder = new Sprite(); addChild(visHolder); initUsername(); initProgressBar(); } private function initUsername():void { // initialize the usernameEntry text field _usernameEntry = new TextField(); _usernameEntry.type = TextFieldType.INPUT; _usernameEntry.defaultTextFormat = usernameEntryFormat; _usernameEntry.width = 250; _usernameEntry.height = usernameEntryFormat.size.valueOf() + 7; _usernameEntry.x = (this.width - _usernameEntry.width) / 2; _usernameEntry.y = (this.height - _usernameEntry.height) / 2; _usernameEntry.border = true; _usernameEntry.borderColor = 0x000000; _usernameEntry.visible = true; _usernameEntry.text = ""; // Initialize the label for the field usernameLabel.x = _usernameEntry.x; usernameLabel.y = _usernameEntry.y - _usernameEntry.height; usernameLabel.visible = true; addChild(_usernameEntry); addChild(usernameLabel); _usernameEntry.addEventListener(KeyboardEvent.KEY_DOWN, usernameKeyDown); } private function initProgressBar():void { progressBar.x = (this.width - progressBar.width) / 2; progressBar.y = _usernameEntry.y + progressBar.height; progressBar.visible = false; progressBar.setMessage("Contacting Last.fm...") progressBar.bar.filters = [new DropShadowFilter(1)]; addChild(progressBar); } private function usernameKeyDown(evt:KeyboardEvent):void { if(evt.keyCode == Keyboard.ENTER) { _usernameEntry.removeEventListener(KeyboardEvent.KEY_DOWN, usernameKeyDown); _usernameEntry.visible = false; usernameLabel.visible = false; _username = _usernameEntry.text; if (getChildByName("errortext")) removeChild(getChildByName("errortext")); usernameRetrieved(); } return; } private function usernameRetrieved():void { progressBar.visible = true; dataProcessor = new DataProcessor(_username); dataProcessor.onProgress = function(prog:Number, stat:String):void { progressBar.progress = prog; progressBar.setMessage(stat); } dataProcessor.onComplete = function():void { progressBar.visible = false; // Show stacked graph initially vis = initStackedGraph(); visHolder.addChild(vis); initLineGraph(); // Start by visualizing tag data: Not using visualizeTags() becuse // that update will conflict with the layout()-called update. filterProperty = TAG_TYPE; _startWeek = dataProcessor.dates[0]; _endWeek = dataProcessor.dates[dataProcessor.dates.length - 1] // addControls(); layout(); fireEvent(VIS_LOADED); } dataProcessor.onError = function(error:String):void { progressBar.visible = false; _usernameEntry.addEventListener(KeyboardEvent.KEY_DOWN, usernameKeyDown); _usernameEntry.visible = true; usernameLabel.visible = true; var errorMessage:TextSprite = new TextSprite(error, new TextFormat("Trebuchet MS", 18, 0x921E11, false)); errorMessage.x = usernameLabel.x; errorMessage.y = _usernameEntry.y + _usernameEntry.height + 5; errorMessage.name = "errortext"; errorMessage.render(); addChild(errorMessage); this.stage.focus = _usernameEntry; } dataProcessor.getData(); } private function initStackedGraph():Visualization { var data:Data = dataProcessor.reshapedData; // Define the visualization stackedVisualization = new Visualization(data); // data.nodes.sortBy("+data.name"); data.nodes.sort = sorter; data.nodes.setProperties({ shape: Shapes.POLYGON, lineColor: 0, fillValue: 1, fillSaturation: 0.5, fillAlpha: 0.6 }); // first, set the visibility according to the query stackedVisualization.operators.add(new VisibilityFilter(filter)); stackedVisualization.operators[0].immediate = true; // filter immediately! stackedVisualization.operators.add(new StackedAreaLayout(dates, 0)); // vis.operators[1].scale.dataMin = -100; // vis.operators[1].scale.dataMax = 100; // vis.operators.add(new AxisLayout("data.date", "data.playcount", true, false)); // Minimum width of stack is 2 pixels //vis.operators[1].threshold = 2;/ vis.operators[1].threshold = 1; stackedVisualization.operators[1].colScale.labelFormat = "MM/dd/yy"; // vis.operators[1].scale = new LogScale(0, 0, 10, true); // Labeler stackedVisualization.operators.add(new StackedAreaLabeler("data.name")); // Column to label stackedVisualization.operators[2].columnIndex = 2; stackedVisualization.operators.add(new ColorEncoder("data.name", Data.NODES, "fillColor", ScaleType.CATEGORIES, ColorPalette.category(data.length/2,null,0.6))); // init axes style stackedVisualization.xyAxes.showBorder = false; with (stackedVisualization.xyAxes.yAxis) { verticalAnchor = TextSprite.BOTTOM; horizontalAnchor = TextSprite.LEFT; labelOffsetX = -40; // offset labels to the right lineCapX1 = 40; // extra line length to the left // lineCapX2 = 15; // extra line length to the right } var upside_down:Boolean = false; if (upside_down) { stackedVisualization.operators[1].orientation = Orientation.TOP_TO_BOTTOM; stackedVisualization.xyAxes.yReverse = true; } // add mouse-over highlight stackedVisualization.controls.add(new HoverControl(NodeSprite, // move highlighted node to be drawn on top HoverControl.MOVE_AND_RETURN, // highlight node to full saturation function(e:SelectionEvent):void { e.node.props.alpha = e.node.fillAlpha; e.node.props.saturation = e.node.fillSaturation; e.node.fillAlpha = 1; e.node.fillSaturation = 1; }, // return node to previous saturation function(e:SelectionEvent):void { e.node.fillAlpha = e.node.props.alpha; e.node.fillSaturation = e.node.props.saturation; } )); // add move node to bottom on click stackedVisualization.controls.add(new ClickControl(NodeSprite, 1, // set search query to the artist name function(e:SelectionEvent):void { // invertDataBelow(e.node); if (focusedNode != null) { focusedNode.data.sort = 0; stackedVisualization.data.nodes.remove(focusedNode); stackedVisualization.data.nodes.add(focusedNode); } e.node.data["sort"] = 1; focusedNode = e.node; vis.data.nodes.remove(e.node); vis.data.nodes.add(e.node); _t = stackedVisualization.update(_timeRangeDur); _t.play(); /* exact = true; // force an exact search search.query = e.node.data["name"]; */ } )); // Add drill-down on double click stackedVisualization.controls.add(new ClickControl(NodeSprite, 2, drillDown)); // add tooltips stackedVisualization.controls.add(new TooltipControl(NodeSprite, null, // update on both roll-over and mouse-move updateToolTipStacked, updateToolTipStacked)); return stackedVisualization; } private function initLineGraph():Visualization { // var data:Data = dataProcessor.originalData; // data.removeGroup(DataProcessor.ARTIST_LIST); // data.removeGroup(DataProcessor.TRACK_LIST); var data:Data = new Data(); for each (var node:Object in dataProcessor.tagData) { data.addNode(node); } data.createEdges("data.date", "data.name"); var timeline:OperatorList = new OperatorList( // the banker automatically selects the visualization // bounds to optimize the perception of trends in the chart // new AspectRatioBanker("data.playcount", true, // 700, 500), new AxisLayout("data.date", "data.playcount"), // new ColorEncoder("data.name", Data.EDGES, // "lineColor", ScaleType.CATEGORIES), new ColorEncoder("data.name", Data.NODES, "fillColor", ScaleType.CATEGORIES), // new PropertyEncoder({ lineAlpha: 0, alpha: 0, buttonMode: false }), // new PropertyEncoder({ buttonMode: false, scaleX: 1, // scaleY: 1, size: 0.5, lineWidth:2 }, Data.EDGES) new PropertyEncoder( { lineAlpha: 0.3, size: 0.7, alpha: 0.7, scaleX: 1, scaleY: 1, buttonMode: false, lineWidth: 1 }) ); // data.nodes.setProperties({ lineAlpha:0, size:0.7, alpha: 0.7, fillSaturation: 0.9 }); timeline[0].xScale.flush = true; // tight margins timeline[0].yScale.flush = true; AxisLayout(timeline[0]).xScale.labelFormat = "MM/dd/yy"; // Uncomment for stacked bar graph: /* timeline[0].yStacked = true; data.nodes.setProperties({ shape: Shapes.VERTICAL_BAR, lineAlpha: 0, size: 2.5 * 700 / vis.data.nodes.length }); */ // data.createEdges("data.date", "data.name"); lineVisualization = new Visualization(data); lineVisualization.operators.add(new VisibilityFilter(filter, Data.EDGES)); lineVisualization.operators[0].immediate = true; // filter immediately! lineVisualization.operators.add(timeline); lineVisualization.operators.add(new VisibilityFilter(filter, Data.NODES)); lineVisualization.operators[2].immediate = true; /* vis.data.nodes.setProperties({ shape: Shapes.HORIZONTAL_BAR, lineAlpha: 0, size: 2.5 * 500 / vis.data.nodes.length }); vis.operators.add(new AxisLayout("data.date", "data.playcount", true, false)); vis.operators.add(new ColorEncoder("data.name", "nodes", "fillColor", ScaleType.CATEGORIES)); vis.xyAxes.yAxis.showLines = false; */ with (lineVisualization.xyAxes.xAxis) { // position axis labels along timeline horizontalAnchor = TextSprite.MIDDLE; verticalAnchor = TextSprite.TOP; fixLabelOverlap = true; showLines = false; } lineVisualization.xyAxes.showBorder = false; with (lineVisualization.xyAxes.yAxis) { verticalAnchor = TextSprite.BOTTOM; horizontalAnchor = TextSprite.LEFT; labelOffsetX = -40; // offset labels to the right lineCapX1 = 40; // extra line length to the left // lineCapX2 = 15; // extra line length to the right } // add mouse-over highlight for nodes lineVisualization.controls.add(new HoverControl(NodeSprite, // move highlighted node to be drawn on top HoverControl.MOVE_AND_RETURN, // highlight node to full saturation function(e:SelectionEvent):void { e.node.props.saturation = e.node.fillSaturation; e.node.props.oldAlpha = e.node.alpha; e.node.fillSaturation = 1; e.node.alpha = 1; for each (var edge:EdgeSprite in lineVisualization.data.edges) { if (edge.data.name == e.node.data.name) { edge.props.oldWidth = edge.lineWidth; edge.lineWidth = 4; // edge.parent.setChildIndex(edge, edge.parent.numChildren-1); } } }, // return node to previous saturation function(e:SelectionEvent):void { e.node.fillSaturation = e.node.props.saturation; e.node.alpha = e.node.props.oldAlpha; name = e.node.data.name; for each (var edge:EdgeSprite in lineVisualization.data.edges) { if (edge.data.name == e.node.data.name) { edge.lineWidth = edge.props.oldWidth; } } } )); // Add mouse-over highlight for edges lineVisualization.controls.add(new HoverControl(EdgeSprite, HoverControl.DONT_MOVE, function(e:SelectionEvent):void { for each (var edge:EdgeSprite in lineVisualization.data.edges) { if (edge.data.name == e.edge.data.name) { edge.props.oldWidth = edge.lineWidth; edge.lineWidth = 4; // edge.parent.setChildIndex(edge, edge.parent.numChildren-1); } } }, function(e:SelectionEvent):void { for each (var edge:EdgeSprite in lineVisualization.data.edges) { if (edge.data.name == e.edge.data.name) { edge.lineWidth = edge.props.oldWidth; } } } )); // add tooltips for nodes and lines lineVisualization.controls.add(new TooltipControl(DataSprite, null, // update on both roll-over and mouse-move updateToolTipLine, updateToolTipLine, null, 0)); // Add drill-down on double click lineVisualization.controls.add(new ClickControl(DataSprite, 2, drillDown)); lineVisualization.update(); return lineVisualization; } public function switchVisualizationStyle():void { var newVis:Visualization = (vis == stackedVisualization ? lineVisualization : stackedVisualization); var oldVis:Visualization = vis; _t = new Transitioner(.5); // Fade out old visualization _t.$(oldVis).alpha = 0; // Fade in new visualization _t.$(newVis).alpha = 1; newVis.alpha = 0; // Don't remove old visualization until faded out // Have to remove it so controls don't interfere _t.addEventListener(TransitionEvent.END, function(e:Event):void { visHolder.removeChild(oldVis); }); // Add newVis on top of old vis (with 0 alpha) visHolder.addChild(newVis); _t.play(); vis = newVis; layout(); } public function changeLineScaleType(type:String):void { var timeline:OperatorList = lineVisualization.operators[1]; timeline[0].yScale.scaleType = type; if (type == "log") timeline[0].yScale.flush = true; else if (type == "linear") timeline[0].yScale.flush = false; _t = lineVisualization.update(_timeRangeDur); _t.play(); } public function changeStackedScaleType(type:String):void { if (stackedVisualization.operators[1].normalize && (type == "Absolute")) { stackedVisualization.operators[1].normalize = false; stackedVisualization.operators[1].scale.labelFormat = "0"; } else if (! stackedVisualization.operators[1].normalize && (type == "Percentage")) { stackedVisualization.operators[1].normalize = true; stackedVisualization.operators[1].scale.labelFormat = "0%"; } _t = stackedVisualization.update(_timeRangeDur); _t.play(); } /** Invoked on double click of a data point to drill down */ private function drillDown(e:SelectionEvent):void { var data:Object = e.node ? e.node.data : e.edge.data; var evt:CheaterEvent = new CheaterEvent(DRILL_DOWN); evt.params["name"] = data["name"]; evt.params["source"] = this; if (filterProperty == TAG_TYPE) { // addFilterProperty(TAG_TYPE, data["name"]); evt.params["type"] = TAG_TYPE; // _tagartist.selectItemWithName("Artists"); } else if (filterProperty == ARTIST_TYPE) { // addFilterProperty(ARTIST_TYPE, data["name"]); // _tagartist.selectItemWithName("Tracks"); evt.params["type"] = ARTIST_TYPE; } else { return; } Application.application.dispatchEvent(evt); } /** * Creates the tooltip text for the stacked graph */ private function updateToolTipStacked(e:TooltipEvent):void { // get current year value from axes, and map to data var date:Date = vis.xyAxes.xAxis.value(vis.mouseX, vis.mouseY) as Date; // Find the closest available date to the cursor date = Dates.addDays(date, 4); date = Dates.roundTime(date,Dates.WEEKS); // All times returned from last.fm are at 12:00 PM UTC date.setUTCHours(12); var def:Boolean = (e.node.data[date] != undefined); if (!def) { // Sometimes dates are stored at 4:00 .... weird GMT/Daylight Savings Issue date = Dates.addHours(date, -1); } updateToolTip(TextSprite(e.tooltip), e.node.data, e.node.data[date], date); } /** * Creates the tooltip text for the line graph */ private function updateToolTipLine(e:TooltipEvent):void { var node:DataSprite = e.node; if (node == null) { node = e.edge; } if (node is EdgeSprite) { var edge:EdgeSprite = node as EdgeSprite; // If mouse is past midpoint, use second node if (vis.mouseX > (edge.x2 - ((edge.x2 - edge.x1)/2))) { node = edge.target; } else { node = edge.source; } } updateToolTip(TextSprite(e.tooltip), node.data, node.data.playcount, node.data.date); } private function updateToolTip(tip:TextSprite, data:Object, playcount:uint, date:Date):void { if(filterProperty == ARTIST_TYPE) { tip.htmlText = Strings.format( "Artist: {0}
Playcount: {1:g}
Week beginning: {2:MM/dd/yy}", data.name, playcount, date); } else if(filterProperty == TRACK_TYPE) { tip.htmlText = Strings.format( "Track: {0}
Artist: {1}
Playcount: {2:g}
Week beginning: {3:MM/dd/yy}", data.name, data.artistName, playcount, date); } else if(filterProperty == TAG_TYPE) { tip.htmlText = Strings.format( "Tag: {0}
Playcount: {1:g}
Week beginning: {2:MM/dd/yy}", data.name, playcount, date); } } /** Changes the current display drill-down to the value given in type: * TAG_TYPE, ARTIST_TYPE, or TRACK_TYPE */ public function changeDrillDownLevel(type:uint, update:Boolean=true):void { filterProperty = type; var input:DataList; if (type == ARTIST_TYPE) { input = dataProcessor.artistData; } else if (type == TAG_TYPE) { input = dataProcessor.tagData; } else if (type == TRACK_TYPE) { input = dataProcessor.trackData; } else { throw new ArgumentError("changeDrillDownLevel: Called with invalid type"); } lineVisualization.data.clear(); for each (var node:Object in input) { lineVisualization.data.addNode(node); } lineVisualization.data.createEdges("data.date", "data.name"); // onFilter(); if (update) { lineVisualization.update(); stackedVisualization.update(); } } /** * Place the visualization and controls. */ private function layout():void { // Layout variables var yMargin:Number = 5; var visWidth:Number = totalWidth - 100; var visHeight:Number = totalHeight - 100; } /** Draws a small simple graph of overall listening, for slider widget. Called from Flex. */ public function drawMiniGraph(x:uint, y:uint, width:uint, height:uint):void { var aggregate:Array = select("date", {playcount: sum("playcount")}). where(eq("type", _(TAG_TYPE))).groupby("date").eval(dataProcessor.originalData.nodes.toDataArray()); var finalNode:Object = {}; for each (var node:Object in aggregate) { finalNode[node.date] = node.playcount; } var data:Data = Data.fromArray([finalNode]); var vis:Visualization = new Visualization(data); data.createEdges("data.date"); data.nodes.setProperties({ shape: Shapes.POLYGON, fillAlpha: 0.9, lineAlpha: 0 }); // vis.operators.add(new AxisLayout("data.date", "data.playcount")); vis.operators.add(new StackedAreaLayout(dates, 0)); vis.bounds = new Rectangle(x, y, width, height); vis.xyAxes.showXLine = false; vis.xyAxes.showYLine = false; vis.xyAxes.showBorder = false; vis.xyAxes.yAxis.showLines = false; vis.xyAxes.xAxis.showLines = false; vis.xyAxes.yAxis.showLabels = false; vis.xyAxes.xAxis.showLabels = false; // vis.operators[0].xScale.flush = true; vis.update(); vis.name = "miniGraph"; addChildAt(vis, this.numChildren-1); } public function removeMiniGraph(time:Number):void { if (this.getChildByName("miniGraph")) { _t = new Transitioner(time); var miniGraph:DisplayObject = this.getChildByName("miniGraph"); _t.$(miniGraph).alpha = 0; _t.addEventListener(TransitionEvent.END, function(e:Event):void { removeChild(miniGraph); }); _t.play(); } } /** Add a filter, such as only a certain tag or artist, to filter set */ public function addFilterProperty(type:uint, value:String):void { if (type == Last_fm_vis.TAG_TYPE) { currentTag = value; } else if (type == Last_fm_vis.ARTIST_TYPE) { currentArtist = value; } else { return; } _t = vis.update(_timeRangeDur); _t.play(); // Update other vis to keep in sync vis == stackedVisualization ? lineVisualization.update() : stackedVisualization.update(); } public function getFilterProperty(type:uint):String { if (type == TAG_TYPE) return currentTag; else if (type == ARTIST_TYPE) return currentArtist; else return null; } public function removeFilterProperty(type:uint):void { if (type == ARTIST_TYPE) { currentArtist = null; } else if (type == TAG_TYPE) { currentTag = null; } _t = vis.update(_timeRangeDur); _t.play(); // Update other vis to keep in sync vis == stackedVisualization ? lineVisualization.update() : stackedVisualization.update(); } public function changeMinDate(ind:int):void { var l:StackedAreaLayout = stackedVisualization.operators[1]; var dateScale:TimeScale = stackedVisualization.operators[1].colScale; if(dateScale.min != dates[ind]) { var i:uint = 0; var j:uint = 0; for(j=0; j < stackedVisualization.data.nodes.length; j++) { var ds:DataSprite = stackedVisualization.data.nodes[j]; var hash:String = ""; if(dateScale.min <= dates[ind]) { for (i=0; i= dates[ind] && dates[i] <= dateScale.min) { if(ds.data[dates[i]] == null) { hash = j.toString() + dates[i].toString(); ds.data[dates[i]] = removedData[hash]; delete removedData[hash]; } i++; } } } l.actualColMin = ind; //l.columns = dates.slice(ind,dates.length); dateScale.min = dates[ind]; _startWeek = dates[ind]; lineVisualization.operators[1][0].xScale.min = dates[ind]; _t = stackedVisualization.update(_timeRangeDur); _t.add(lineVisualization.update(_timeRangeDur)); _t.play(); } } public function changeMaxDate(ind:int):void { var l:StackedAreaLayout = stackedVisualization.operators[1]; var dateScale:TimeScale = stackedVisualization.operators[1].colScale; if(dateScale.max != dates[ind]) { var i:uint = 0; var j:uint = 0; for(j=0; j < stackedVisualization.data.nodes.length; j++) { var ds:DataSprite = stackedVisualization.data.nodes[j]; var hash:String = ""; if(dateScale.max >= dates[ind]) { for (i=dates.length-1; i>ind; i--) { if(ds.data[dates[i]] != null) { hash = j.toString() + dates[i].toString(); removedData[hash] = ds.data[dates[i]]; ds.data[dates[i]] = null; } } } else { i=ind; while(dates[i] <= dates[ind] && dates[i] >= dateScale.max) { if(ds.data[dates[i]] == null) { hash = j.toString() + dates[i].toString(); ds.data[dates[i]] = removedData[hash]; delete removedData[hash]; } i--; } } //l.columns = dates.slice(ind,dates.length); } l.actualColMax = ind; dateScale.max = dates[ind]; _endWeek = dates[ind]; lineVisualization.operators[1][0].xScale.max = dates[ind]; _t = stackedVisualization.update(_timeRangeDur); _t.add(lineVisualization.update(_timeRangeDur)); _t.play(); } } public function testRemove():void { trace("In testRemove..."); // var chartNodes:DataList = chartData.nodes; // var tempArray:Array = chartData.nodes.toDataArray(); // removedData.add(chartNodes.list[0]); // vis.data.remove(temp[0]); var dateScale:TimeScale = vis.operators[1].colScale; trace(vis.operators[1].scale.dataMin + " " + vis.operators[1].scale.dataMax); trace(dateScale.min + " " + dateScale.max); trace(dateScale.scaleMin + " " + dateScale.scaleMax); dateScale.min = dates[3]; _t = vis.update(_timeRangeDur); _t.play(); } public function testAdd():void { trace("In testAdd..."); // var temp:DataSprite = removedData.toDataArray()[0]; // removedData.remove(temp); // vis.data.addNode(temp); var dateScale:TimeScale = vis.operators[1].colScale; dateScale.min = dates[0]; _t = vis.update(_timeRangeDur); _t.play(); var scaleValues:Array = vis.operators[1].colScale.values(); for each (var d:Date in scaleValues) trace(d); } public function testData():void { var temp:Array = chartData.nodes.toDataArray(); trace("rawChartData[0][dates[0]]: " + rawChartData[0]["artist"]); trace("nodes.length: " + temp.length + "\ntemp[0]: " + temp[15]["artist"]); } /** Filter function for determining visibility. */ private function filter(d:DataSprite):Boolean { if (d is EdgeSprite) { // Source (node to the left) has the data we want d = EdgeSprite(d).source; } if (vis == lineVisualization) { // Since we are in line graph view, filter based on date // (stacked graph has to manually alter data) if (d.data.date < _startWeek) return false; // Must be greater than or equal to because we don't want the edges sticking out of the final nodes // (since edges take the source from the left) if (d.data.date >= _endWeek) return false; } var prop:String = String(d.data["name"]); // Filter based on double-clicked tag if (filterProperty != d.data["type"]) { return false; } if (currentTag != null) { if ((d.data.type == TAG_TYPE && d.data.name != currentTag) || (d.data.type == ARTIST_TYPE && dataProcessor.artistTags[prop] != currentTag) || (d.data.type == TRACK_TYPE && dataProcessor.artistTags[d.data.artistName] != currentTag)) return false; } if (currentArtist != null) { if ((d.data.type == ARTIST_TYPE && d.data.name != currentArtist) || (d.data.type == TAG_TYPE && dataProcessor.artistTags[currentArtist] != d.data.name) || (d.data.type == TRACK_TYPE && d.data.artistName != currentArtist)) return false; } if (!query || query.length==0) { return true; } else { var s:String = prop.toLowerCase(); for each (var q:String in query) { var len:int = q.length; if (len == 0) continue; // Search anywhere in string, not just beginning if (!exact && (s.search(q) != -1)) return true; if (exact && q==s) return true; } return false; } } /** Callback for filter events. */ public function onFilter(evt:Event=null):void { var search:SearchBox = evt["target"]; query = search.query.toLowerCase().split(/\|/); if (query.length==1 && query[0].length==0) query.pop(); if (_t && _t.running) _t.stop(); _t = vis.update(_dur); _t.play(); lineVisualization.operators[1][0].yScale.updateBinding(); exact = false; // reset exact match after each search // Update other vis to keep in sync vis == stackedVisualization ? lineVisualization.update() : stackedVisualization.update(); } // -------------------------------------------------------------------- // -------------------------------------------------------------------- public static function printArray(a:Array):void { var i:Object, j:Object; var output:String = ""; for(i in a) { for(j in a[i]) output += a[i][j].toString() + " "; output += "\n"; } trace(output); } private function fireEvent(type:String):void { var evt:CheaterEvent = new CheaterEvent(type); evt.params["source"] = this; Application.application.dispatchEvent(evt); } override public function getExplicitOrMeasuredHeight():Number { return totalHeight; } override public function getExplicitOrMeasuredWidth():Number { return totalWidth; } } }