package flare.vis.data { import flare.scale.LinearScale; import flare.scale.LogScale; import flare.scale.OrdinalScale; import flare.scale.QuantileScale; import flare.scale.QuantitativeScale; import flare.scale.RootScale; import flare.scale.Scale; import flare.scale.ScaleType; import flare.scale.TimeScale; import flare.util.Stats; import flare.vis.events.DataEvent; /** * Utility class that binds a data property to a descriptive scale. * A ScaleBinding provides a layer of indirection between a data field and * a data scale describing that field. The created scale can be used for * layout and encoding of data values. When scale parameters such as the * scale type or value range are updated, an underlying scale instance will * be updated accordingly or a new instance will be created as needed. */ public class ScaleBinding extends Scale { /** @private */ protected var _scale:Scale = null; /** @private */ protected var _scaleType:String = null; /** @private */ protected var _pmin:Object = null; /** @private */ protected var _pmax:Object = null; /** @private */ protected var _base:Number = 10; /** @private */ protected var _bins:int = 5; /** @private */ protected var _power:Number = NaN; /** @private */ protected var _zeroBased:Boolean = false; /** @private */ protected var _ordinals:Vector. = null; /** @private */ protected var _property:String; /** @private */ protected var _group:String; /** @private */ protected var _data:Data; /** @private */ protected var _stats:Stats; /** If true, updates to the underlying data will be ignored, as will * any calls to updateBinding. Set this flag if you want * to prevent the scale values from changing automatically. */ public var ignoreUpdates:Boolean = false; /** The type of scale to create. */ public override function get scaleType():String { return _scaleType ? _scaleType : scale.scaleType; } public function set scaleType(type:String):void { _scaleType = type; _scale = null; } /** The preferred minimum data value for the scale. If null, the scale * minimum will be determined from the data directly. */ public function get preferredMin():Object { return _pmin; } public function set preferredMin(val:Object):void { _pmin = val; if (_scale && _pmin) { _scale.min = _pmin; if (_zeroBased) zeroAlignScale(_scale); } } /** The preferred maximum data value for the scale. If null, the scale * maximum will be determined from the data directly. */ public function get preferredMax():Object { return _pmax; } public function set preferredMax(val:Object):void { _pmax = val; if (_scale && _pmax) { _scale.max = _pmax; if (_zeroBased) zeroAlignScale(_scale); } } /** @inheritDoc */ public override function get max():Object { return scale.max; } public override function set max(v:Object):void { scale.max = v; } /** @inheritDoc */ public override function get min():Object { return scale.min; } public override function set min(v:Object):void { scale.min = v; } /** The number base to use for a quantitative scale (10 by default). */ public function get base():Number { return _base; } public function set base(val:Number):void { _base = val; if (_scale is QuantitativeScale) { QuantitativeScale(_scale).base = _base; } } /** A free parameter that indicates the exponent for a RootScale. */ public function get power():Number { return _power; } public function set power(val:Number):void { _power = val; if (_scale is RootScale) { RootScale(_scale).power = _power; } } /** The number of bins for quantile scales. */ public function get bins():int { return _bins; } public function set bins(count:int):void { _bins = count; if (_scale is QuantileScale) { _scale = null; } } /** Flag indicating if the scale bounds should be flush with the data. * @see flare.scale.Scale#flush */ public override function get flush():Boolean { return _flush; } public override function set flush(val:Boolean):void { _flush = val; if (_scale) _scale.flush = _flush; } /** Formatting pattern for formatting labels for scale values. * @see flare.vis.scale.Scale#labelFormat. */ public override function get labelFormat():String { return _format; } public override function set labelFormat(fmt:String):void { _format = fmt; if (_scale) _scale.labelFormat = fmt; } /** Flag indicating if a zero-based scale should be used. If set to * true, and the scale type is numerical, the minimum or maximum * scale value will automatically be adjusted to include the zero * point as necessary. */ public function get zeroBased():Boolean { return _zeroBased; } public function set zeroBased(val:Boolean):void { _zeroBased = val; if (_scale) zeroAlignScale(_scale); } /** An ordered object vector of values for defining an ordinal scale. */ public function get ordinals():Vector. { return _ordinals; } public function set ordinals(ord:Vector.):void { _ordinals = ord; if (ScaleType.isOrdinal(_scaleType)) { _stats = null; _scale = null; } } // ----------------------------------------------------- /** The data instance to bind to. */ public function get data():Data { return _data; } public function set data(data:Data):void { if (_data != null) { // remove existing listeners _data.removeEventListener(DataEvent.ADD, onDataEvent); _data.removeEventListener(DataEvent.REMOVE, onDataEvent); _data.removeEventListener(DataEvent.UPDATE, onDataEvent); } _data = data; if (_data != null) { // add new listeners _data.addEventListener(DataEvent.ADD, onDataEvent, false, 0, true); _data.addEventListener(DataEvent.REMOVE, onDataEvent, false, 0, true); _data.addEventListener(DataEvent.UPDATE, onDataEvent, false, 0, true); } } /** The data group to bind to. */ public function get group():String { return _group; } public function set group(name:String):void { if (name != _group) { _group = name; _stats = null; _scale = null; } } /** The data property to bind to. */ public function get property():String { return _property; } public function set property(name:String):void { if (name != _property) { _property = name; _stats = null; _scale = null; } } /** The underlying scale created by this binding. */ protected function get scale():Scale { if (!_data || !_group || !_property) { throw new Error("Can't create scale with data to bind to."); } if (!_scale) { _stats = _data.group(_group).stats(_property); _scale = buildScale(_stats); } return _scale; } // -------------------------------------------------------------------- /** * Creates a new ScaleBinding. */ public function ScaleBinding() { } /** * Checks to see if the binding is current. If not, the internal stats * and scale for this binding will be cleared and lazily recomputed. * @return true if the binding was updated, false otherwise */ public function updateBinding():Boolean { if (ignoreUpdates) return false; var stats:Stats = _data.group(_group).stats(_property); if (stats !== _stats) { // object identity test _stats = null; _scale = null; return true; } return false; } /** * Internal listener for data events that clears the current scale * instance as needed. * @param evt a DataEvent */ private function onDataEvent(evt:DataEvent):void { if (ignoreUpdates) return; if (evt.list.name == _group) { if (evt.type == DataEvent.UPDATE) { updateBinding(); } else { _stats = null; _scale = null; } } } /** @inheritDoc */ public override function clone() : Scale { return scale.clone(); } /** * Returns the index of the input value in the ordinal object vector if the * scale is ordinal or categorical, otherwise returns -1. * @param value the value to lookup * @return the index of the input value. If the value is not contained * in the ordinal object vector, this method returns -1. */ public function index(value:Object):int { var s:OrdinalScale = scale as OrdinalScale; return (s ? s.index(value) : -1); } /** The number of distinct values in this scale, if ordinal. */ public function get length():int { var s:OrdinalScale = scale as OrdinalScale; return (s ? s.length : -1); } /** @inheritDoc */ public override function interpolate(value:Object) : Number { return scale.interpolate(value); } /** @inheritDoc */ public override function lookup(f:Number) : Object { return scale.lookup(f); } /** @inheritDoc */ public override function values(num:int=-1) : Vector. { return scale.values(num); } /** @inheritDoc */ public override function label(value:Object) : String { return scale.label(value); } /** @private */ protected function buildScale(stats:Stats):Scale { var type:String = _scaleType ? _scaleType : ScaleType.UNKNOWN; var vals:Vector. = _ordinals ? _ordinals : stats.distinctValues; var scale:Scale; switch (stats.dataType) { case Stats.NUMBER: switch (type) { case ScaleType.LINEAR: case ScaleType.UNKNOWN: scale = new LinearScale(stats.minimum, stats.maximum, _base, _flush, _format); break; case ScaleType.ROOT: var pow:Number = isNaN(_power) ? 2 : _power; scale = new RootScale(stats.minimum, stats.maximum, _base, _flush, pow, _format); break; case ScaleType.LOG: scale = new LogScale(stats.minimum, stats.maximum, _base, _flush, _format); break; case ScaleType.QUANTILE: scale = new QuantileScale(_bins, stats.values, true, _format); break; default: scale = new OrdinalScale(vals, _flush, false, _format); break; } break; case Stats.DATE: switch (type) { case ScaleType.UNKNOWN: case ScaleType.LINEAR: case ScaleType.TIME: scale = new TimeScale(stats.minDate, stats.maxDate, _flush, _format); break; default: scale = new OrdinalScale(vals, _flush, false, _format); break; } break; default: scale = new OrdinalScale(vals, _flush, false, _format); break; } if (_pmin) scale.min = _pmin; if (_pmax) scale.max = _pmax; if (_zeroBased) zeroAlignScale(scale); return scale; } private static function zeroAlignScale(scale:Scale):void { if (scale is QuantitativeScale) { var qs:QuantitativeScale = QuantitativeScale(scale); if (qs.scaleMin > 0) qs.dataMin = 0; if (qs.scaleMax < 0) qs.dataMax = 0; } } } // end of class ScaleBinding }