package flare.vis.data { import flare.animate.Transitioner; import flare.util.Filter; import flare.util.IEvaluable; import flare.util.Property; import flare.util.Sort; import flare.util.Stats; import flare.util.Vectors; import flare.util.math.DenseMatrix; import flare.util.math.IMatrix; import flare.vis.events.DataEvent; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import flash.utils.Dictionary; import flash.utils.Proxy; import flash.utils.flash_proxy; [Event(name="add", type="flare.vis.events.DataEvent")] [Event(name="remove", type="flare.vis.events.DataEvent")] /** * Data structure for a collection of DataSprite instances. * Items contained in this list can be accessed using array notation * ([]), iterated over using the for each * construct, or can be processed by passing a visitor function to the * visit method. * *

Data lists provide methods for sorting elements both in a one-time * and persistent fashion, for setting the properties of contained * items in a batch-processing style (see the setProperty * and setProperties methods), and for computing and * caching summary statistics of data variables (see the * stats method.

* *

Data lists also support listeners for add and remove events. These * events are fired before the add or remove is executed. These * data events can be canceled by calling preventDefault() * on the DataEvent object, thereby preventing the add or * remove from being performed. Using this mechanism, clients can add * custom constraints on the contents of a data list by adding new * listeners that monitor add and remove events and cancel them when * desired.

*/ public class DataList extends Proxy implements IEventDispatcher { private var _dispatch:EventDispatcher = new EventDispatcher(); /** Hashed set of items in the data list. */ private var _map:Dictionary = new Dictionary(); /** Array of items in the data set. */ private var _list:Vector. = new Vector.(); /** Default property values to be applied to new items. */ private var _defs:Object = null; /** Cache of Stats objects for item properties. */ private var _stats:Object = {}; /** The underlying array storing the list. */ internal function get list():Vector. { return _list; } /** The name of this data list. */ public function get name():String { return _name; } private var _name:String; /** Internal count of visitors traversing the current list. */ private var _visiting:int = 0; private var _sort:Sort; /** The number of items contained in this list. */ public function get length():int { return _list.length; } /** A standing sort criteria for items in the list. */ public function get sort():Sort { return _sort; } public function set sort(s:*):void { _sort = s==null ? s : (s is Sort ? Sort(s) : new Sort(s)); if (_sort != null) _sort.sort(_list); } // -------------------------------------------------------------------- /** * Creates a new DataList instance. * @param editable indicates if this list should be publicly editable. */ public function DataList(name:String) { _name = name; } // -- Basic Operations: Contains, Add, Remove, Clear ------------------ /** * Indicates if the given object is contained in this list. * @param d the object to check for containment * @return true if the list contains the object, false otherwise. */ public function contains(d:DataSprite):Boolean { return (_map[d] != undefined); } /** * Add a DataSprite to the list. * @param d the DataSprite to add * @return the added DataSprite, or null if the add failed */ public function add(d:DataSprite):DataSprite { if (!fireEvent(DataEvent.ADD, d)) return null; _map[d] = _list.length; _stats = {}; if (_sort != null) { var idx:int = Vectors.binarySearch(_list, d, null, _sort.comparator); _list.splice(-(idx+1), 0, d); } else { _list.push(d); } return d; } /** * Remove a data sprite from the list. * @param ds the DataSprite to remove * @return true if the object was found and removed, false otherwise */ public function remove(d:DataSprite):Boolean { if (_map[d] == undefined) return false; if (!fireEvent(DataEvent.REMOVE, d)) return false; if (_visiting > 0) { // if called from a visitor, use a copy-on-write strategy _list = Vectors.copy(_list); _visiting = 0; // reset the visitor count } Vectors.remove(_list, d); delete _map[d]; _stats = {}; return true; } /** * Remove a DataSprite from the list. * @param idx the index of the DataSprite to remove * @return the removed DataSprite */ public function removeAt(idx:int):DataSprite { var d:DataSprite = _list[idx] as DataSprite; if (d == null || !fireEvent(DataEvent.REMOVE, d)) return null; Vectors.removeAt(_list, idx); if (d != null) { delete _map[d]; _stats = {}; } return d; } /** * Remove all DataSprites from this list. */ public function clear():Boolean { if (_list.length == 0) return true; if (!fireEvent(DataEvent.REMOVE, _list)) return false; _map = new Dictionary(); _list.length = 0; _stats = {}; return true; } // -- Data Representations -------------------------------------------- /** * Returns an array of data objects for each item in this data list. * Data objects are retrieved from the "data" property for each item. * @return an array of data objects for items in this data list */ public function toDataArray():Array { var a:Array = new Array(_list.length); for (var i:int=0; i { var a:Vector. = new Vector.(_list.length); for (var i:int=0; iNodeSprite instances. * The method takes an optional function to compute edge weights. * @param w the edge weight function. This function should take an * EdgeSprite as input and return a Number. * @param mat a matrix instance in which to store the adjacency matrix * values. If this value is null, a new DenseMatrix will * be constructed. * @return the adjacency matrix */ public function adjacencyMatrix(w:Function=null, mat:IMatrix=null):IMatrix { var N:int = length, k:int = 0; // build dictionary of nodes var idx:Dictionary = new Dictionary(); for (k=0; kDataSprite instances and return a Number * @param mat a matrix instance in which to store the adjacency matrix * values. If this value is null, a new DenseMatrix will * be constructed. * @return the distance matrix */ public function distanceMatrix(d:Function, mat:IMatrix=null):IMatrix { var N:int = length, i:uint, j:uint; if (mat) { mat.init(N, N); } else { mat = new DenseMatrix(N, N); } for (i=0; isort property. * @param args the sort arguments. * If a String is provided, the data will be sorted in ascending order * according to the data field named by the string. * If an Array is provided, the data will be sorted according to the * fields in the array. In addition, field names can optionally * be followed by a boolean value. If true, the data is sorted in * ascending order (the default). If false, the data is sorted in * descending order. */ public function sortBy(...args):void { if (args.length == 0) return; if (args[0] is Array) args = args[0]; var f:Function = Sort.$(args); _list.sort(f); } // -- Visitation ------------------------------------------------------ /** * Iterates over the contents of the list, invoking a visitor function * on each element of the list. If the visitor function returns a * Boolean true value, the iteration will stop with an early exit. * @param visitor the visitor function to be invoked on each item * @param filter an optional boolean-valued function indicating which * items should be visited * @param reverse optional flag indicating if the list should be * visited in reverse order * @return true if the visitation was interrupted with an early exit */ public function visit(visitor:Function, filter:*=null, reverse:Boolean=false):Boolean { _visiting++; // mark a visit in process var a:Vector. = _list; // use our own reference to the list var i:uint, n:uint=a.length, b:Boolean = false; var f:Function = Filter.$(filter); if (reverse && f==null) { for (i=n; --i>=0;) if (visitor(a[i]) as Boolean) { b = true; break; } } else if (reverse) { for (i=n; --i>=0;) if (f(a[i]) && (visitor(a[i]) as Boolean)) { b = true; break; } } else if (f==null) { for (i=0; i *
  • If the value is a Function, it will be evaluated * for each element and the result will be used as the property * value for that element.
  • *
  • If the value is an IEvaluable instance, such as * flare.util.Property or * flare.query.Expression, it will be evaluated for * each element and the result will be used as the property value * for that element.
  • *
  • In all other cases, the property value will be treated as a * literal and assigned for all elements.
  • * * @param name the name of the property * @param value the value of the property * @param t a transitioner or time span for updating object values. If * the input is a transitioner, it will be used to store the updated * values. If the input is a number, a new Transitioner with duration * set to the input value will be used. The input is null by default, * in which case object values are updated immediately. * @param filter an optional Boolean-valued filter function for * limiting which items are visited * @return the transitioner used to update the values */ public function setProperty(name:String, value:*, t:*=null, filter:*=null):Transitioner { var trans:Transitioner = Transitioner.instance(t); var f:Function = Filter.$(filter); Vectors.setProperty(_list, name, value, f, trans); return trans; } /** * Sets property values on all sprites in a given group. The values * within the vals argument can take a number of forms: *
      *
    • If a value is a Function, it will be evaluated * for each element and the result will be used as the property * value for that element.
    • *
    • If a value is an IEvaluable instance, such as * flare.util.Property or * flare.query.Expression, it will be evaluated for * each element and the result will be used as the property value * for that element.
    • *
    • In all other cases, a property value will be treated as a * literal and assigned for all elements.
    • *
    * @param vals an object containing the properties and values to set. * @param t a transitioner or time span for updating object values. If * the input is a transitioner, it will be used to store the updated * values. If the input is a number, a new Transitioner with duration * set to the input value will be used. The input is null by default, * in which case object values are updated immediately. * @param filter an optional Boolean-valued filter function for * limiting which items are visited * @return the transitioner used to update the values */ public function setProperties(vals:Object, t:*=null, filter:*=null):Transitioner { var trans:Transitioner = Transitioner.instance(t); var f:Function = Filter.$(filter); for (var name:String in vals) Vectors.setProperty(_list, name, vals[name], f, trans); return trans; } /** * A function generator that can be used to set properties * at a later time. This method returns a function that can * accept a Transitioner as its sole argument and then * executes the setProperties method. * @param vals an object containing the properties and values to set. * This is treated the same as the setProperties method. * @param filter an optional Boolean-valued filter function for * limiting which items are visited * @return a function that accepts a Transitioner argument * and runs setProperties. */ public function setLater(vals:Object, filter:*=null):Function { return function(t:Transitioner=null):Transitioner { return setProperties(vals, t, filter); } } // -- Statistics ------------------------------------------------------ /** * Computes and caches statistics for a data field. The resulting * Stats object is cached, so that later access does not * require any re-calculation. The cache of statistics objects may be * cleared, however, if changes to the data set are made. * @param field the property name * @return a Stats object with the computed statistics */ public function stats(field:String):Stats { // TODO: allow custom comparators? // check cache for stats if (_stats[field] != undefined) { return _stats[field] as Stats; } else { return _stats[field] = new Stats(_list, field); } } /** * Clears any cached stats for the given field. * @param field the data field to clear the stats for. */ public function clearStats(field:String):void { delete _stats[field]; } // -- Event Dispatcher Methods ---------------------------------------- /** @private */ protected function fireEvent(type:String, items:*):Boolean { if (_dispatch.hasEventListener(type)) { return _dispatch.dispatchEvent( new DataEvent(type, items, this)); } return true; } /** @inheritDoc */ public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false) : void { _dispatch.addEventListener(type, listener, useCapture, priority, useWeakReference); } /** @inheritDoc */ public function dispatchEvent(event:Event):Boolean { return _dispatch.dispatchEvent(event); } /** @inheritDoc */ public function hasEventListener(type:String):Boolean { return _dispatch.hasEventListener(type); } /** @inheritDoc */ public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void { _dispatch.removeEventListener(type, listener, useCapture); } /** @inheritDoc */ public function willTrigger(type:String):Boolean { return _dispatch.willTrigger(type); } // -- Proxy Methods --------------------------------------------------- /** @private */ flash_proxy override function getProperty(name:*):* { return _list[name]; } /** @private */ flash_proxy override function setProperty(name:*, value:*):void { this.setProperty(name, value); } /** @private */ flash_proxy override function nextNameIndex(idx:int):int { return (idx < _list.length ? idx + 1 : 0); } /** @private */ flash_proxy override function nextName(idx:int):String { return String(idx-1); } /** @private */ flash_proxy override function nextValue(idx:int):* { return _list[idx-1]; } } // end of class DataList }