PageRenderTime 153ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/flare/vis/legend/LegendRange.as

https://github.com/pombredanne/Elastic-Lists
ActionScript | 336 lines | 227 code | 40 blank | 69 comment | 35 complexity | cf9b2c35dc503bf8fd63f4a981f1df57 MD5 | raw file
  1. package flare.vis.legend {
  2. import flare.display.RectSprite;
  3. import flare.display.TextSprite;
  4. import flare.scale.IScaleMap;
  5. import flare.scale.Scale;
  6. import flare.util.Colors;
  7. import flare.util.Orientation;
  8. import flare.util.Stats;
  9. import flare.util.Vectors;
  10. import flare.util.palette.ColorPalette;
  11. import flash.display.GradientType;
  12. import flash.display.Graphics;
  13. import flash.display.Shape;
  14. import flash.display.Sprite;
  15. import flash.geom.Matrix;
  16. import flash.geom.Rectangle;
  17. import flash.text.TextFormat;
  18. /**
  19. * A range in a continuous legend, consisting of a continuous
  20. * visual scale and value labels. Legend ranges use a
  21. * <code>ColorPalette</code> instance for creating a gradient of
  22. * color values. If the <code>stats</code> property
  23. * is set with the <code>Stats</code> object for a backing data
  24. * variable, a histogram of values will also be drawn in the legend
  25. * range. To draw only a histogram, set the <code>palette</code>
  26. * property to null.
  27. */
  28. public class LegendRange extends RectSprite implements IScaleMap
  29. {
  30. private var _dataField:String;
  31. private var _scale:Scale;
  32. private var _stats:Stats;
  33. private var _palette:ColorPalette;
  34. private var _matrix:Matrix = new Matrix();
  35. private var _margin:Number = 5;
  36. private var _labels:Sprite;
  37. private var _fmt:TextFormat;
  38. private var _labelMode:int = TextSprite.BITMAP;
  39. private var _range:Shape;
  40. private var _rs:Number = 20;
  41. private var _borderColor:uint = 0xcccccc;
  42. private var _histogramColor:uint = 0x888888;
  43. private var _orient:String;
  44. private var _vert:Boolean;
  45. private var _rev:Boolean;
  46. /** The data field described by this legend range. */
  47. public function get dataField():String { return _dataField; }
  48. /** Sprite containing the range's labels. */
  49. public function get labels():Sprite { return _labels; }
  50. /** Stats object describing the data range. */
  51. public function get stats():Stats { return _stats; }
  52. public function set stats(s:Stats):void { _stats = s; dirty(); }
  53. /** Text format (font, size, style) of legend range labels. */
  54. public function get labelTextFormat():TextFormat { return _fmt; }
  55. public function set labelTextFormat(fmt:TextFormat):void {
  56. _fmt = fmt; dirty();
  57. }
  58. /** TextFormat (font, size, style) of legend range labels. */
  59. public function get labelTextMode():int { return _labelMode; }
  60. public function set labelTextMode(mode:int):void {
  61. _labelMode = mode; dirty();
  62. }
  63. /** Margin value for padding within the legend item. */
  64. public function get margin():Number { return _margin; }
  65. public function set margin(m:Number):void {
  66. _margin = m; dirty();
  67. }
  68. /** The size of the range, this is either the width or height of
  69. * the range, depending on the current orientation. */
  70. public function get rangeSize():Number { return _rs; }
  71. public function set rangeSize(s:Number):void { _rs = s; }
  72. /** The color of the legend range border. */
  73. public function get borderColor():uint { return _borderColor; }
  74. public function set borderColor(c:uint):void {
  75. if (c != _borderColor) { _borderColor = c; dirty(); }
  76. }
  77. /** The color of bars in a generated histogram. */
  78. public function get histogramColor():uint { return _histogramColor; }
  79. public function set histogramColor(c:uint):void {
  80. if (c == _histogramColor) return;
  81. _histogramColor = c;
  82. if (_stats) dirty();
  83. }
  84. /** The desired orientation of this legend range. */
  85. public function get orientation():String { return _orient; }
  86. public function set orientation(o:String):void {
  87. _orient = o;
  88. _vert = Orientation.isVertical(o);
  89. _rev = o==Orientation.RIGHT_TO_LEFT || o==Orientation.BOTTOM_TO_TOP;
  90. dirty();
  91. }
  92. // --------------------------------------------------------------------
  93. /**
  94. * Creates a new LegendRange.
  95. * @param dataField the data field described by this range
  96. * @param palette the color palette for the data field
  97. * @param scale the Scale instance mapping the data field to a visual
  98. * variable
  99. */
  100. public function LegendRange(dataField:String, scale:Scale,
  101. palette:ColorPalette=null,
  102. orientation:String=Orientation.LEFT_TO_RIGHT)
  103. {
  104. _dataField = dataField;
  105. _palette = palette;
  106. _scale = scale;
  107. this.orientation = orientation;
  108. addChild(_range = new Shape());
  109. addChild(_labels = new Sprite());
  110. _range.cacheAsBitmap = true;
  111. }
  112. // --------------------------------------------------------------------
  113. // Lookup
  114. /** @inheritDoc */
  115. public function get x1():Number {
  116. return _vert || !_rev ? _margin : _w-_margin;
  117. }
  118. /** @inheritDoc */
  119. public function get x2():Number {
  120. return _vert ? _margin+_rs : (_rev ? _margin : _w-_margin);
  121. }
  122. /** @inheritDoc */
  123. public function get y1():Number {
  124. return _vert && !_rev ? _h-_margin : _margin;
  125. }
  126. /** @inheritDoc */
  127. public function get y2():Number {
  128. return _vert ? (_rev ? _h-_margin : _margin) : _margin+_rs;
  129. }
  130. private var _bounds:Rectangle = new Rectangle();
  131. /**
  132. * Bounds for the visual range portion of this legend range.
  133. * @return the bounds of the range display
  134. */
  135. public function get bounds():Rectangle {
  136. _bounds.x = x1;
  137. _bounds.y = y1;
  138. _bounds.width = x2 - x1;
  139. _bounds.height = y2 - y1;
  140. return _bounds;
  141. }
  142. /** @inheritDoc */
  143. public function value(x:Number, y:Number, stayInBounds:Boolean=true):Object
  144. {
  145. var f:Number;
  146. if (_vert) {
  147. f = (y-_margin) / (_h - 2*_margin);
  148. } else {
  149. f = (x-_margin) / (_w - 2*_margin);
  150. }
  151. // correct bounds
  152. if (stayInBounds) {
  153. if (f < 0) f = 0;
  154. if (f > 1) f = 1;
  155. }
  156. if (_rev) f = 1-f;
  157. // lookup and return value
  158. return _scale.lookup(f);
  159. }
  160. /** @inheritDoc */
  161. public function X(val:Object):Number
  162. {
  163. return x1 + (_vert ? 0 : _scale.interpolate(val) * (x2 - x1));
  164. }
  165. /** @inheritDoc */
  166. public function Y(val:Object):Number
  167. {
  168. return y1 + (_vert ? _scale.interpolate(val) * (y2-y1) : y1);
  169. }
  170. // --------------------------------------------------------------------
  171. // Layout and Render
  172. /**
  173. * Update the labels shown by this legend range.
  174. */
  175. public function updateLabels():void
  176. {
  177. var pts:Vector.<Object> = _palette==null ? Vectors.copyFromArray([0,1]) : _palette.keyframes;
  178. var n:uint = pts.length;
  179. // filter for the needed number of labels
  180. for (var i:uint=_labels.numChildren; i<n; ++i) {
  181. _labels.addChild(new TextSprite());
  182. }
  183. for (i=_labels.numChildren; --i>=n;) {
  184. _labels.removeChildAt(i);
  185. }
  186. // update and layout the labels
  187. for (i=0; i<n; ++i) {
  188. var ts:TextSprite = TextSprite(_labels.getChildAt(i));
  189. var val:Object = _scale.lookup(pts[i] as Number);
  190. // set format
  191. if (_fmt != null) ts.applyFormat(_fmt);
  192. ts.textMode = _labelMode;
  193. // set text
  194. ts.text = _scale.label(val);
  195. // set text label alignment
  196. var j:uint = _vert==_rev ? i : n-i-1;
  197. ts.horizontalAnchor = _vert || j==0 ? TextSprite.LEFT :
  198. j==n-1 ? TextSprite.RIGHT : TextSprite.CENTER;
  199. ts.verticalAnchor = !_vert || j==0 ? TextSprite.TOP :
  200. j==n-1 ? TextSprite.BOTTOM : TextSprite.MIDDLE;
  201. // set position
  202. ts.x = _vert ? x2 : X(val);
  203. ts.y = _vert ? Y(val) : y2;
  204. var offset:Number = ts.height / 5;
  205. if (_vert) {
  206. ts.x += offset;
  207. ts.y += j==0 ? -offset/2 : (j==n-1 ? offset : 0);
  208. } else if (j==n-1) {
  209. ts.x += offset/2;
  210. }
  211. ts.render();
  212. }
  213. // TODO adjust visibility based on overlap?
  214. }
  215. /** @inheritDoc */
  216. public override function render():void
  217. {
  218. updateLabels();
  219. var w:Number = _vert ? _rs : _w - 2*_margin;
  220. var h:Number = _vert ? _h - 2*_margin : _rs;
  221. _range.x = _margin;
  222. _range.y = _margin;
  223. _h = _vert ? _h : 2*margin + h + _labels.height;
  224. _range.graphics.clear();
  225. if (_palette != null)
  226. drawPalette(w, h);
  227. if (_stats != null)
  228. drawHistogram(w, h);
  229. _range.graphics.lineStyle(0, _borderColor);
  230. _range.graphics.drawRect(0, 0, w, h);
  231. }
  232. /**
  233. * Draws a histogram of data values in the range dispay.
  234. * @param w the width of the range display
  235. * @param h the height of the range display
  236. */
  237. protected function drawHistogram(w:Number, h:Number):void
  238. {
  239. var values:Vector.<Object> = _stats.values;
  240. var ib:int = int(_vert ? h/2 : w/2);
  241. var pb:int = (_vert ? h : w) / ib;
  242. var d:Number = _vert ? w : h;
  243. var i:int, f:Number;
  244. var counts:Vector.<int> = new Vector.<int>(ib);
  245. for (i=0; i<counts.length; ++i) counts[i] = 0;
  246. for (i=0; i<values.length; ++i) {
  247. f = _scale.interpolate(values[i]);
  248. var idx:int = int(Math.round(f*(ib-1)));
  249. counts[idx]++;
  250. }
  251. var max:Number = 0;
  252. for (i=0; i<counts.length; ++i) {
  253. if (counts[i] > max) max = counts[i];
  254. }
  255. max = d / (1.1*max);
  256. var g:Graphics = _range.graphics;
  257. g.beginFill(_histogramColor, _palette ? 0.5 : 1);
  258. for (i=0; i<ib; ++i) {
  259. var j:int = _vert==_rev ? i : ib-i-1;
  260. if (_vert)
  261. g.drawRect(w, h*i/ib, -max*counts[j], pb);
  262. else
  263. g.drawRect(w*i/ib, h, pb, -max*counts[j]);
  264. }
  265. g.endFill();
  266. }
  267. /**
  268. * Draws a continuous color range in the range display.
  269. * @param w the width of the range display
  270. * @param h the height of the range display
  271. */
  272. protected function drawPalette(w:Number, h:Number):void
  273. {
  274. // build gradient paint parameters
  275. var N:int = _palette.keyframes.length;
  276. var colors:Array = new Array(N);
  277. var alphas:Array = new Array(N);
  278. var ratios:Array = new Array(N);
  279. for (var i:uint=0; i<N; ++i) {
  280. var c:uint = _palette.getColor(_palette.keyframes[i] as Number);
  281. colors[i] = 0x00ffffff & c;
  282. alphas[i] = Colors.a(c) / 255;
  283. ratios[i] = int(255 * (_palette.keyframes[i] as Number));
  284. }
  285. var rot:Number = _vert ? (_rev ? 1 : -1) * Math.PI/2
  286. : (_rev ? Math.PI : 0);
  287. _matrix.createGradientBox(w, h, rot);
  288. // paint the color palette
  289. var g:Graphics = _range.graphics;
  290. g.beginGradientFill(GradientType.LINEAR,
  291. colors, alphas, ratios, _matrix);
  292. g.drawRect(0, 0, w, h);
  293. g.endFill();
  294. }
  295. } // end of class LegendRange
  296. }