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