package lastfmvis { import flare.animate.TransitionEvent; import flare.animate.Transitioner; import flare.display.TextSprite; import flare.vis.legend.LegendItem; import flare.widgets.SearchBox; import flash.display.DisplayObject; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Rectangle; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import mx.core.UIComponent; public class LastfmVisHolder extends UIComponent { private var visualizations:Array = []; /** Search box */ private var search:SearchBox; /** Tag/Artist switch */ private var _tagartist:RadioSwitch; /** Container for current filters in use */ private var _filtersDisplay:Sprite; /** Linear/Log Switch for Line Graph */ private var _lineScaleSwitch:RadioSwitch; /** Percent/Absolute Switch for Stacked Graph */ private var _stackedScaleSwitch:RadioSwitch; /** Vis Style Switch */ private var _visStyleSwitch:RadioSwitch; /** Container for current usernames and remove buttons */ private var _usernameDisplay:Sprite; // Vis Styles public const STACKED_STYLE:uint = 0; public const LINE_STYLE:uint = 1; /** Pixel value for left-alignment of widgets along right side of Visualization */ private static const WIDGET_X:uint = 730; /** Format for small text labels on right side */ public static const SMALL_FMT:TextFormat = new TextFormat("Arial",14,0,false); private var currentStyle:uint; private var _dates:Array; public function get usernameEntries():Array { var ret:Array = []; for each (var vis:Last_fm_vis in visualizations) { ret.push(vis.usernameEntry); } return ret; } public function get dates():Array { if (_dates) return _dates; _dates = []; var foundDates:Object = {}; for each (var vis:Last_fm_vis in visualizations) { for each (var ADate:Date in vis.dates) { foundDates[ADate] = true; } } for (var date:String in foundDates) { _dates.push(new Date(date)); } _dates.sortOn("time"); return _dates; } public function LastfmVisHolder(x:uint, height:uint) { super(); this.x = x; this.y = y; addControls(); disableControls(); addVisualization(); } public function visLoaded(vis:Last_fm_vis):void { vis.bounds = new Rectangle(0, 0, vis.width - 100, vis.height - 100); enableControls(); addNewUser(vis); // New visualization if (vis != visualizations[0]) { //vis.y -= 100; var firstVis:Last_fm_vis = visualizations[0]; if (currentStyle != STACKED_STYLE) vis.switchVisualizationStyle(); if (_tagartist.selectedItemName == "Artists") vis.changeDrillDownLevel(Last_fm_vis.ARTIST_TYPE); else if (_tagartist.selectedItemName == "Tracks") vis.changeDrillDownLevel(Last_fm_vis.TRACK_TYPE); vis.addFilterProperty(Last_fm_vis.ARTIST_TYPE, firstVis.getFilterProperty(Last_fm_vis.ARTIST_TYPE)); vis.addFilterProperty(Last_fm_vis.TAG_TYPE, firstVis.getFilterProperty(Last_fm_vis.TAG_TYPE)); // Force search event dispatch (this is when getters and setters are kinda ugly) search.query = search.query; } } /** * Add interactivity controls: search box, time range adjuster, * etc. */ private function addControls():void { // create search box search = new SearchBox(Last_fm_vis.FMT, ">", 250); search.borderColor = 0xdedede; search.input.tabIndex = 0; search.addEventListener(SearchBox.SEARCH, filterAll); search.y = 5; search.x = 0; addChild(search); // Create Visualization Style Toggle var visStyleText:Array = ["Stacked Graph", "Line Graph"]; _visStyleSwitch = new RadioSwitch( function(item:LegendItem):void { if (item.name == visStyleText[0] && currentStyle != STACKED_STYLE) { currentStyle = STACKED_STYLE; switchAllStyles(); } else if (item.name == visStyleText[1] && currentStyle != LINE_STYLE) { currentStyle = LINE_STYLE; switchAllStyles(); } }, null, null, visStyleText); _visStyleSwitch.x = WIDGET_X; _visStyleSwitch.y = 20; addChild(_visStyleSwitch); // Create Tag/Artist Toggle var tagArtistText:Array = ["Tags", "Artists", "Tracks"]; _tagartist = new RadioSwitch( function(item:LegendItem):void { if (item.name == tagArtistText[1]) { changeAllDrillDownLevel(Last_fm_vis.ARTIST_TYPE, true); } else if (item.name == tagArtistText[0]) { changeAllDrillDownLevel(Last_fm_vis.TAG_TYPE, true); } else if (item.name == tagArtistText[2]) { changeAllDrillDownLevel(Last_fm_vis.TRACK_TYPE, true); } }, null, null, tagArtistText); _tagartist.x = WIDGET_X; _tagartist.y = 160; addChild(_tagartist); // Linear / Log Switch var switchText:Array = ["Linear", "Logarithmic"]; _lineScaleSwitch = new RadioSwitch( function(item:LegendItem):void { for each (var vis:Last_fm_vis in visualizations) { if (item.name == switchText[0]) { vis.changeLineScaleType("linear"); } else { vis.changeLineScaleType("log"); } } }, null, null, switchText); _lineScaleSwitch.x = WIDGET_X; _lineScaleSwitch.y = 90; _lineScaleSwitch.alpha = 0; // this.addChild(_lineScaleSwitch); // % / Absolute Display Switch var stackedSwitchText:Array = ["Absolute", "Percentage"]; _stackedScaleSwitch = new RadioSwitch( function(item:LegendItem):void { for each (var vis:Last_fm_vis in visualizations) { vis.changeStackedScaleType(item.name); } }, null, null, stackedSwitchText); _stackedScaleSwitch.x = WIDGET_X; _stackedScaleSwitch.y = 90; this.addChild(_stackedScaleSwitch); // Filter info area // _filtersDisplay = new RectSprite(WIDGET_X, 250, 170, 100, 10, 10); _filtersDisplay = new Sprite(); _filtersDisplay.x = WIDGET_X; _filtersDisplay.y = 250; // _filtersDisplay.width = 170; var label:TextSprite = new TextSprite("Filters:", SMALL_FMT); label.x = 2; label.y = 2; _filtersDisplay.addChild(label); // RectSprite(_filtersDisplay).render(); label.render(); var infoText:TextField = new TextField(); infoText.text = "Double-click on a tag or artist to filter based on that tag or artist."; infoText.width = 170; infoText.wordWrap = true; infoText.setTextFormat(new TextFormat("Arial", 14, 0x909090, false)); infoText.name = "info"; infoText.y = label.y + label.height; infoText.x = label.x; _filtersDisplay.addChild(infoText); addChild(_filtersDisplay); /* _usernameDisplay = new Sprite(); _usernameDisplay.x = WIDGET_X; _usernameDisplay.y = 450; label = new TextSprite("Showing:", SMALL_FMT); label.x = 2; label.y = 2; _usernameDisplay.addChild(label); label.render() addChild(_usernameDisplay); */ } public function filterChanged(type:uint, value:String, source:Last_fm_vis):void { var finalString:String; _tagartist.disableFunctions(); if (type == Last_fm_vis.TAG_TYPE) { finalString = "Tag: "; _tagartist.selectItemAt(1); changeAllDrillDownLevel(Last_fm_vis.ARTIST_TYPE, false); } else if (type == Last_fm_vis.ARTIST_TYPE) { finalString = "Artist: " _tagartist.selectItemAt(2); changeAllDrillDownLevel(Last_fm_vis.TRACK_TYPE, false); } else { return; } _tagartist.enableFunctions(); // Remove info text if (_filtersDisplay.getChildByName("info") != null) { _filtersDisplay.removeChild(_filtersDisplay.getChildByName("info")); } finalString += "" + value + ""; var container:Sprite = new Sprite(); container.x = 2; var text:TextField = new TextField(); text.htmlText = finalString; text.setTextFormat(new TextFormat("Arial", 16, 0, false)); text.width = 170; text.wordWrap = true; var closeButton:TextSprite = new TextSprite("x", new TextFormat("Arial", 16, 0xA02814, false)); closeButton.buttonMode = true; text.x = closeButton.width + 2; // text.height = text.textHeight; text.autoSize = TextFieldAutoSize.LEFT; closeButton.render(); container.addChild(text); container.addChild(closeButton); closeButton.addEventListener(MouseEvent.ROLL_OVER, function(e:Event):void { closeButton.bold = true; }); closeButton.addEventListener(MouseEvent.ROLL_OUT, function(e:Event):void { closeButton.bold = false; }); closeButton.addEventListener(MouseEvent.CLICK, function(e:Event):void { _filtersDisplay.removeChild(container); for each (var vis:Last_fm_vis in visualizations) { vis.removeFilterProperty(type); } }); // 5 pixels below last child in _filtersDisplay var temp:DisplayObject = _filtersDisplay.getChildAt(0); container.y = _filtersDisplay.getChildAt(0).y + _filtersDisplay.getChildAt(0).height + 3; _filtersDisplay.addChildAt(container, 0); for each (var vis:Last_fm_vis in visualizations) { vis.addFilterProperty(type, value); } search.query = ""; // Dispatches update event -- watch out! } public function addVisualization():void { if (visualizations.length == 0) { addFirstVisualization(); return; } var delay:Number = .5; var prevvis:Last_fm_vis = visualizations[0]; prevvis.removeMiniGraph(delay); var newVis:Last_fm_vis = new Last_fm_vis(prevvis.x, prevvis.y + prevvis.height/2 - 10, prevvis.width, 0); newVis.usernameY += 100; var t:Transitioner = new Transitioner(delay); prevvis.height = prevvis.height / 2 + 50; prevvis.reBound(delay); newVis.height = prevvis.height // newVis.y += 100; // newVis.opaqueBackground = 0xFF0000; /* Debugging */ // newVis.reBound(delay); visualizations.push(newVis); this.addChildAt(newVis, 0); t.play(); } public function addFirstVisualization():void { var firstVis:Last_fm_vis = new Last_fm_vis(0, search.y + search.height + 5); this.height = firstVis.height; this.width = firstVis.width; visualizations.push(firstVis); currentStyle = STACKED_STYLE; this.addChildAt(visualizations[0], 0); } public function addNewUser(vis:Last_fm_vis):void { /* var container:Sprite = new Sprite(); container.x = 2; var text:TextField = new TextField(); text.text = vis.username; text.setTextFormat(new TextFormat("Arial", 16, 0, false)); text.width = 170; text.wordWrap = true; var closeButton:TextSprite = new TextSprite("x", new TextFormat("Arial", 16, 0xA02814, false)); closeButton.buttonMode = true; text.x = closeButton.width + 2; text.autoSize = TextFieldAutoSize.LEFT; closeButton.render(); container.addChild(text); container.addChild(closeButton); closeButton.addEventListener(MouseEvent.ROLL_OVER, function(e:Event):void { closeButton.bold = true; }); closeButton.addEventListener(MouseEvent.ROLL_OUT, function(e:Event):void { closeButton.bold = false; }); closeButton.addEventListener(MouseEvent.CLICK, function(e:Event):void { removeChild(vis); if (visualizations.length == 1) { visualizations.pop() addVisualization(); } else { visualizations = visualizations.splice(visualizations.indexOf(vis), 1); visualizations[0].height = 2 * (visualizations[0].height - 50); visualizations[0].reBound(0); } _usernameDisplay.removeChild(container); }); // 5 pixels below last child in _filtersDisplay var temp:DisplayObject = _usernameDisplay.getChildAt(0); container.y = temp.y + temp.height + 3; _usernameDisplay.addChildAt(container, 0); */ } public function drawMiniGraph(x:uint, y:uint, width:uint, height:uint):void { if (visualizations.length > 1) { // TODO: Should probably get rid of mini graph... return; } visualizations[0].drawMiniGraph(x, y, width, height); } public function changeMinDate(ind:int):void { for each (var vis:Last_fm_vis in visualizations) { vis.changeMinDate(ind); } } public function changeMaxDate(ind:int):void { for each (var vis:Last_fm_vis in visualizations) { vis.changeMaxDate(ind); } } private function filterAll(evt:Event):void { for each (var vis:Last_fm_vis in visualizations) { vis.onFilter(evt); } } private function disableControls():void { _tagartist.disable(); _stackedScaleSwitch.disable(); _lineScaleSwitch.disable(); _visStyleSwitch.disable(); } private function enableControls():void { _tagartist.enable(); _stackedScaleSwitch.enable(); _lineScaleSwitch.enable(); _visStyleSwitch.enable(); } private function switchAllStyles():void { var t:Transitioner = new Transitioner(.5); if (currentStyle == STACKED_STYLE) { // The style we're switching _TO_ this.addChild(_stackedScaleSwitch); _stackedScaleSwitch.alpha = 0; t.$(_stackedScaleSwitch).alpha = 1; t.$(_lineScaleSwitch).alpha = 0; t.addEventListener(TransitionEvent.END, function(e:Event):void { removeChild(_lineScaleSwitch); }); } else if (currentStyle == LINE_STYLE) { this.addChild(_lineScaleSwitch); _lineScaleSwitch.alpha = 0; t.$(_stackedScaleSwitch).alpha = 0; t.$(_lineScaleSwitch).alpha = 1; t.addEventListener(TransitionEvent.END, function(e:Event):void { removeChild(_stackedScaleSwitch); }); } t.play(); for each (var vis:Last_fm_vis in visualizations) { vis.switchVisualizationStyle(); } } private function changeAllDrillDownLevel(type:uint, update:Boolean):void { for each (var vis:Last_fm_vis in visualizations) { vis.changeDrillDownLevel(type, update); } } } }