PageRenderTime 121ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/flare/vis/legend/Legend.as

https://github.com/pombredanne/Elastic-Lists
ActionScript | 586 lines | 333 code | 63 blank | 190 comment | 74 complexity | 6618e0228316e3f491b8ad21cc86e87c MD5 | raw file
  1. package flare.vis.legend {
  2. import flare.animate.Transitioner;
  3. import flare.display.RectSprite;
  4. import flare.display.TextSprite;
  5. import flare.scale.Scale;
  6. import flare.scale.ScaleType;
  7. import flare.util.Displays;
  8. import flare.util.Orientation;
  9. import flare.util.Vectors;
  10. import flare.util.palette.ColorPalette;
  11. import flare.util.palette.Palette;
  12. import flare.util.palette.ShapePalette;
  13. import flare.util.palette.SizePalette;
  14. import flare.vis.data.ScaleBinding;
  15. import flash.display.Sprite;
  16. import flash.geom.Rectangle;
  17. import flash.text.TextFormat;
  18. /**
  19. * A legend describing the visual encoding of a data property. Legends
  20. * support both discrete legends that list individual items and
  21. * range legends the convey a continuous range of values. Discrete
  22. * legends consist of a collection of <code>LegendItem</code> instances
  23. * stored in the <code>items</code> sprite. Range legends consist of
  24. * a single <code>LegendRange</code> instance stored in the
  25. * <code>items</code> sprite.
  26. *
  27. * <p>There are multiple ways to generate a legend. To build a legend
  28. * based on an existing visual encoding, use the static
  29. * <code>fromScale</code> constructor. This method takes a data scale
  30. * and one more or more palettes (e.g., color, shape, or size palettes)
  31. * and uses them to generate an appropriate legend. If the data scale
  32. * is a quantitative scale and only a color palette is provided, a
  33. * continuous range legend will be generated. Otherwise, a discrete
  34. * legend will be created.</p>
  35. *
  36. * <p>Legends can also be created from a collection of independent
  37. * values using the static <code>fromValues</code> constructor. This
  38. * method takes an Object Vector or Array of legend item descriptions
  39. * and uses them to generate a legend. For example, consider this code:</p>
  40. *
  41. * <pre>
  42. * var legend:Legend = Legend.fromValues("Legend Title", [
  43. * {color: 0xff0000, shape:Shapes.X, label:"Red X"},
  44. * {color: 0x00ff00, shape:Shapes.SQUARE, label:"Green Square"},
  45. * {color: 0x0000ff, shape:Shapes.CIRCLE, label:"Blue Circle"}
  46. * ]);
  47. * </pre>
  48. *
  49. * <p>This example will create a legend with the described values.
  50. * See the documentation for the <code>buildFromValues</code> method
  51. * for more details.</p>
  52. */
  53. public class Legend extends Sprite
  54. {
  55. /** @private The layout bounds for this legend instance. */
  56. protected var _bounds:Rectangle = null;
  57. /** @private Sprite defining the border of the legend. */
  58. protected var _border:RectSprite;
  59. /** @private Sprite containing the legend items. */
  60. protected var _items:Sprite;
  61. /** @private TextSprite containing the legend title.*/
  62. protected var _title:TextSprite;
  63. /** @private Scale instance used to define the legend mapping. */
  64. protected var _scale:Scale;
  65. /** @private Flag for if this legend is discrete or continuous. */
  66. protected var _discrete:Boolean = true;
  67. /** @private The default color to use for legend items. */
  68. protected var _defaultColor:uint = 0xff888888;
  69. /** @private The color palette used to encode values (may be null). */
  70. protected var _colors:ColorPalette;
  71. /** @private The shape palette used to encode values (may be null). */
  72. protected var _shapes:ShapePalette;
  73. /** @private The size palette used to encode values (may be null). */
  74. protected var _sizes:SizePalette;
  75. /** @private Flag indicating the desired orientation of this legend. */
  76. protected var _orient:String = null;
  77. /** @private Margins within legend items. */
  78. protected var _margin:Number = 4;
  79. /** @private Spacing between legend items. */
  80. protected var _spacing:Number = 0;
  81. /** @private Base icon size */
  82. protected var _baseIconSize:Number = 12;
  83. /** @private TextFormat (font, size, style) of legend item labels. */
  84. protected var _labelTextFormat:TextFormat = new TextFormat("Arial",12,0);
  85. /** @private Label text mode. */
  86. protected var _labelTextMode:int = TextSprite.BITMAP;
  87. /** @private The calculated internal width of the legend. */
  88. protected var _iw:Number;
  89. /** @private The calculated internal height of the legend. */
  90. protected var _ih:Number;
  91. // -- Properties ------------------------------------------------------
  92. /** The layout bounds for this legend instance. */
  93. public function get bounds():Rectangle { return _bounds; }
  94. public function set bounds(b:Rectangle):void { _bounds = b; }
  95. /** Sprite defining the border of the legend. */
  96. public function get border():RectSprite { return _border; }
  97. /** Sprite containing the legend items. */
  98. public function get items():Sprite { return _items; }
  99. /** TextSprite containing the legend title.*/
  100. public function get title():TextSprite { return _title; }
  101. /** Flag indicating if this legend is discrete or continuous. */
  102. public function get discrete():Boolean { return _discrete; }
  103. /** Scale instance used to define the legend mapping. */
  104. public function get scale():Scale { return _scale; }
  105. public function set scale(s:Scale):void { _scale = s; }
  106. /** The legend range, if this legend is continuous. This
  107. * value is null if the legend is discrete. */
  108. public function get range():LegendRange {
  109. return _discrete ? null : LegendRange(_items.getChildAt(0));
  110. }
  111. /** The default color to use for legend items. */
  112. public function get defaultColor():uint { return _defaultColor; }
  113. public function set defaultColor(c:uint):void { _defaultColor = c; }
  114. /** The color palette used to encode values (may be null). */
  115. public function get colorPalette():ColorPalette { return _colors; }
  116. public function set colorPalette(cp:ColorPalette):void { _colors = cp; }
  117. /** The shape palette used to encode values (may be null). */
  118. public function get shapePalette():ShapePalette { return _shapes; }
  119. public function set shapePalette(sp:ShapePalette):void { _shapes = sp; }
  120. /** The size palette used to encode values (may be null). */
  121. public function get sizePalette():SizePalette { return _sizes; }
  122. public function set sizePalette(sp:SizePalette):void { _sizes = sp; }
  123. /** The desired orientation of this legend. */
  124. public function get orientation():String { return _orient; }
  125. public function set orientation(o:String):void { _orient = o; }
  126. /** Margins within legend items. */
  127. public function get margin():Number { return _margin; }
  128. public function set margin(m:Number):void { _margin = m; }
  129. /** Spacing between legend items. */
  130. public function get spacing():Number { return _spacing; }
  131. public function set spacing(s:Number):void { _spacing = s; }
  132. /** Base icon size, corresponding to a size factor of 1. */
  133. public function get baseIconSize():Number { return _baseIconSize; }
  134. public function set baseIconSize(s:Number):void {
  135. if (_baseIconSize != s && _discrete) {
  136. for (var i:uint=0; i<_items.numChildren; ++i) {
  137. var li:LegendItem = LegendItem(_items.getChildAt(i));
  138. li.iconSize *= (s / _baseIconSize);
  139. li.maxIconSize *= (s / _baseIconSize);
  140. }
  141. }
  142. _baseIconSize = s;
  143. }
  144. /** TextFormat (font, size, style) of legend item labels. */
  145. public function get labelTextFormat():TextFormat { return _labelTextFormat; }
  146. public function set labelTextFormat(f:TextFormat):void {
  147. _labelTextFormat = f; updateItems();
  148. }
  149. /** Label text mode. */
  150. public function get labelTextMode():int { return _labelTextMode; }
  151. public function set labelTextMode(mode:int):void {
  152. _labelTextMode = mode; updateItems();
  153. }
  154. // -- Initialization --------------------------------------------------
  155. /**
  156. * Creates a new Legend for the given data field.
  157. * @param dataField the data field to describe with the legend
  158. * @param vis the visualization corresponding to this legend
  159. * @param scale the scale value used to map the data field to visual
  160. * variables
  161. */
  162. public function Legend(title:String, scale:Scale=null,
  163. colors:ColorPalette=null, shapes:ShapePalette=null,
  164. sizes:SizePalette=null)
  165. {
  166. this.scale = scale;
  167. addChild(_border = new RectSprite(0,0,0,0,13,13));
  168. addChild(_title = new TextSprite());
  169. addChild(_items = new Sprite());
  170. _border.lineColor = 0;
  171. _border.fillColor = 0;
  172. _colors = colors;
  173. _shapes = shapes;
  174. _sizes = sizes;
  175. _title.textField.defaultTextFormat =
  176. new TextFormat("Arial",12,null,true);
  177. if (title != null)
  178. _title.text = title;
  179. else
  180. _title.visible = false;
  181. if (scale != null) {
  182. buildFromScale();
  183. update();
  184. }
  185. }
  186. /**
  187. * Update the legend, recomputing layout of items.
  188. * @param t a transitioner for value updates
  189. * @return the input transitioner
  190. */
  191. public function update(t:Transitioner=null):Transitioner
  192. {
  193. if (_scale is ScaleBinding && ScaleBinding(_scale).updateBinding())
  194. buildFromScale();
  195. updateItems();
  196. layout(t);
  197. return t;
  198. }
  199. /**
  200. * Builds the contents of this legend from the current scale values.
  201. * This method will remove all items from the legend and rebuild the
  202. * legend using the current scale and palette settings.
  203. */
  204. public function buildFromScale():void
  205. {
  206. // first, remove all items
  207. while (_items.numChildren > 0) {
  208. _items.removeChildAt(_items.numChildren-1);
  209. }
  210. // determine legend type
  211. var type:String = _scale.scaleType;
  212. if (ScaleType.isQuantitative(type) && !_sizes && !_shapes) {
  213. // build continuous legend
  214. _discrete = false;
  215. if (!_orient) _orient = Orientation.LEFT_TO_RIGHT;
  216. _items.addChild(new LegendRange(_title.text,
  217. _scale, _colors, _orient));
  218. } else {
  219. // build discrete legend
  220. _discrete = true;
  221. if (!_orient) _orient = Orientation.TOP_TO_BOTTOM;
  222. var numVals:int = ScaleType.isQuantitative(type) ? 5 : -1;
  223. var maxSize:Number = Number.MIN_VALUE;
  224. var vals:Vector.<Object> = _scale.values(numVals);
  225. for (var i:uint=0; i<vals.length; ++i) {
  226. // determine legend item properties
  227. var f:Number = _scale.interpolate(vals[i]);
  228. var color:uint = _defaultColor;
  229. if (_colors && ScaleType.isOrdinal(type)) {
  230. color = _colors.getColorByIndex(i);
  231. } else if (_colors) {
  232. color = _colors.getColor(f);
  233. }
  234. var shape:String = _shapes ? _shapes.getShape(i) : null;
  235. var size:Number = _baseIconSize*(_sizes?_sizes.getSize(f):1);
  236. if (size > maxSize) maxSize = size;
  237. var item:LegendItem = new LegendItem(
  238. _scale.label(vals[i]), color, shape, size);
  239. item.value = vals[i];
  240. _items.addChild(item);
  241. }
  242. for (i=0; i<_items.numChildren; ++i)
  243. LegendItem(_items.getChildAt(i)).maxIconSize = maxSize;
  244. }
  245. }
  246. /**
  247. * Populates the contents of this legend from a list of value objects.
  248. * This method will create a legend with discrete entries determined by
  249. * the contents of the input <code>values</code> array or vector. This should be
  250. * an array or vector of objects containing the following properties:
  251. * <ul>
  252. * <li><code>value</code>: The data value the legend item represents.
  253. * This value is not required.</li>
  254. * <li><code>label</code>: The text label to place in the legend item.
  255. * If this value is not provided, the method will attempt to
  256. * generate a label string from the <code>value</code> property.</li>
  257. * <li><code>color</code>: The color for the legend item. If missing,
  258. * this legend's default color will be used.</li>
  259. * <li><code>shape</code>: The shape for the legend item. If missing,
  260. * a default circle shape will be used.</li>
  261. * <li><code>size</code>: The size for the legend item. If missing,
  262. * a size value of 1 will be used.</li>
  263. * </ul>
  264. * When this method is called, any previous values in the legend will
  265. * be removed.
  266. * @param valuesv an array or vector of value to include in the legend.
  267. */
  268. public function buildFromValues(valuesv:*):void
  269. {
  270. var values:Vector.<Object>;
  271. if(valuesv is Array)
  272. {
  273. values = Vectors.copyFromArray(valuesv);
  274. }
  275. else if(valuesv is Vector.<Object>)
  276. {
  277. values = valuesv;
  278. }
  279. else
  280. {
  281. throw new Error("In flare.vis.legend, buildFromValues(), the parameter" +
  282. "was not a valid Array or Vector.<Object> instance.");
  283. }
  284. // first, remove all items
  285. while (_items.numChildren > 0) {
  286. _items.removeChildAt(_items.numChildren-1);
  287. }
  288. _discrete = true;
  289. if (!_orient) _orient = Orientation.TOP_TO_BOTTOM;
  290. var maxSize:Number = Number.MIN_VALUE;
  291. for each (var v:Object in values) {
  292. var value:Object = v.value != undefined ? v.value : null;
  293. var label:String = v.label ? v.label.toString() : value ? value.toString() : "???";
  294. var color:uint = v.color != undefined ? uint(v.color) : _defaultColor;
  295. var shape:String = v.shape ? v.shape as String : null;
  296. var size:Number = _baseIconSize*(v.size is Number ? v.size : 1);
  297. if (size > maxSize) maxSize = size;
  298. var item:LegendItem = new LegendItem(label, color, shape, size);
  299. item.value = value;
  300. item.margin = margin;
  301. _items.addChild(item);
  302. }
  303. for (var i:uint=0; i<_items.numChildren; ++i)
  304. LegendItem(_items.getChildAt(i)).maxIconSize = maxSize;
  305. }
  306. // -- Layout ----------------------------------------------------------
  307. /**
  308. * Performs layout, setting the position for all items in the legend.
  309. * @param t a transitioner for value updates
  310. */
  311. public function layout(t:Transitioner=null):void
  312. {
  313. t = (t ? t : Transitioner.DEFAULT);
  314. var vert:Boolean = Orientation.isVertical(_orient);
  315. var o:Object;
  316. var b:Rectangle = bounds;
  317. var x:Number = b ? b.left : 0;
  318. var y:Number = b ? b.top : 0;
  319. var w:Number, h:Number, th:Number = 0;
  320. // layout the title
  321. o = t.$(_title);
  322. if (_title.text != null && _title.text.length > 0) {
  323. o.x = x + _margin;
  324. o.alpha = 1;
  325. _title.visible = true;
  326. y += (th = _title.height + (vert?_spacing:0));
  327. } else {
  328. o.alpha = 0;
  329. o.visible = false;
  330. }
  331. // layout item container
  332. o = t.$(_items);
  333. o.x = x;
  334. o.y = y;
  335. // layout items
  336. if (_discrete) {
  337. layoutDiscrete(t);
  338. } else {
  339. layoutContinuous(t);
  340. }
  341. w = b ? (vert ? b.width : Math.min(_iw, b.width)) : _iw;
  342. h = b ? (vert ? Math.min(_ih, b.height) : _ih) : _ih;
  343. // size the border
  344. o = t.$(_border);
  345. o.x = x;
  346. o.y = b ? b.top : 0;
  347. o.w = w;
  348. o.h = h + th;
  349. if (t.immediate) _border.render();
  350. // create clipping panel
  351. t.$(items).scrollRect = new Rectangle(0, 0, 1+w, 1+h);
  352. }
  353. /**
  354. * @private
  355. * Layout helper for positioning discrete legend items.
  356. * @param t a transitioner for value updates
  357. */
  358. protected function layoutDiscrete(t:Transitioner):void
  359. {
  360. var vert:Boolean = Orientation.isVertical(_orient);
  361. var rev:Boolean = _orient == Orientation.RIGHT_TO_LEFT ||
  362. _orient == Orientation.BOTTOM_TO_TOP;
  363. var x:Number = 0, y:Number = 0, i:uint, j:uint;
  364. var item:LegendItem, o:Object;
  365. var bw:Number = vert && bounds ? bounds.width : NaN;
  366. // if needed, compute shared width for legend items
  367. if (vert && isNaN(bw)) {
  368. bw = Number.MIN_VALUE;
  369. for (i=0; i<_items.numChildren; ++i) {
  370. item = _items.getChildAt(i) as LegendItem;
  371. bw = Math.max(bw, item.innerWidth);
  372. }
  373. }
  374. _iw = _ih = 0;
  375. for (i=0; i<_items.numChildren; ++i) {
  376. j = rev ? _items.numChildren-i-1 : i;
  377. // layout the item
  378. item = _items.getChildAt(j) as LegendItem;
  379. o = t.$(item);
  380. o.x = x;
  381. o.y = y;
  382. o.w = isNaN(bw) ? item.innerWidth : bw;
  383. // increment spacing
  384. if (vert) {
  385. y += item.innerHeight + _spacing;
  386. _iw = Math.max(_iw, item.innerWidth);
  387. }
  388. else {
  389. x += item.innerWidth + _spacing;
  390. _ih = Math.max(_ih, item.innerHeight);
  391. }
  392. }
  393. _iw = vert ? _iw : x-_spacing;
  394. _ih = vert ? y-_spacing : _ih;
  395. }
  396. /**
  397. * @private
  398. * Layout helper for positioning a continous legend range.
  399. * @param trans a transitioner for value updates
  400. */
  401. protected function layoutContinuous(t:Transitioner):void
  402. {
  403. var lr:LegendRange = _items.getChildAt(0) as LegendRange;
  404. lr.orientation = _orient;
  405. if (Orientation.isHorizontal(_orient)) {
  406. _iw = lr.w = bounds ? bounds.width : 200;
  407. lr.updateLabels();
  408. _ih = lr.height + lr.margin;
  409. } else {
  410. _ih = lr.h = bounds ? bounds.height : 200;
  411. lr.updateLabels();
  412. _iw = lr.width;
  413. }
  414. }
  415. // -- Legend Items ----------------------------------------------------
  416. /** @private */
  417. protected function updateItems() : void
  418. {
  419. if (_items.numChildren == 0) {
  420. return;
  421. } else if (_discrete) {
  422. for (var i:uint = 0; i<_items.numChildren; ++i)
  423. updateItem(_items.getChildAt(i) as LegendItem);
  424. } else {
  425. updateRange(_items.getChildAt(0) as LegendRange);
  426. }
  427. }
  428. /** @private */
  429. protected function updateItem(item:LegendItem):void
  430. {
  431. item.label.textMode = _labelTextMode;
  432. item.label.applyFormat(_labelTextFormat);
  433. item.margin = _margin;
  434. }
  435. /** @private */
  436. protected function updateRange(range:LegendRange):void
  437. {
  438. range.labelTextMode = _labelTextMode;
  439. range.labelTextFormat = _labelTextFormat;
  440. range.margin = _margin;
  441. }
  442. /**
  443. * Sets property values on all legend items. The values
  444. * within the <code>vals</code> argument can take a number of forms:
  445. * <ul>
  446. * <li>If a value is a <code>Function</code>, it will be evaluated
  447. * for each element and the result will be used as the property
  448. * value for that element.</li>
  449. * <li>If a value is an <code>IEvaluable</code> instance, such as
  450. * <code>flare.util.Property</code> or
  451. * <code>flare.query.Expression</code>, it will be evaluated for
  452. * each element and the result will be used as the property value
  453. * for that element.</li>
  454. * <li>In all other cases, a property value will be treated as a
  455. * literal and assigned for all elements.</li>
  456. * </ul>
  457. * @param vals an object containing the properties and values to set.
  458. * @param t a transitioner or time span for updating object values. If
  459. * the input is a transitioner, it will be used to store the updated
  460. * values. If the input is a number, a new Transitioner with duration
  461. * set to the input value will be used. The input is null by default,
  462. * in which case object values are updated immediately.
  463. * @return the transitioner used to update the values
  464. */
  465. public function setItemProperties(vals:Object, t:*=null):Transitioner
  466. {
  467. var trans:Transitioner = Transitioner.instance(t);
  468. Displays.setChildrenProperties(items, vals, trans);
  469. return trans;
  470. }
  471. // -- Static Constructors ---------------------------------------------
  472. /**
  473. * Generates a legend from a given scale and one or more palettes. If
  474. * multiple palettes of the same type are provided, only the first of
  475. * each type will be used for the legend, the others will be ignored.
  476. * @param title the title text for the legend
  477. * @param scale the scale instance determining the legend values
  478. * @param palette a color, shape, or size palette for legend items
  479. * @param args one or more additional palettes
  480. * @return the generated Legend, or null if a legend could not be built
  481. */
  482. public static function fromScale(title:String, scale:Scale,
  483. palette:Palette, ...args):Legend
  484. {
  485. if (scale == null || palette == null)
  486. return null;
  487. var colors:ColorPalette;
  488. var shapes:ShapePalette;
  489. var sizes:SizePalette;
  490. // construct palette collection
  491. var palettes:Array = args as Array;
  492. palettes.unshift(palette);
  493. for each (var p:Palette in palettes) {
  494. if (p is ColorPalette && !colors)
  495. colors = ColorPalette(p);
  496. else if (p is ShapePalette && !shapes)
  497. shapes = ShapePalette(p);
  498. else if (p is SizePalette && !sizes)
  499. sizes = SizePalette(p);
  500. }
  501. if (!colors && !shapes && !sizes)
  502. return null; // no palette to use
  503. return new Legend(title, scale, colors, shapes, sizes);
  504. }
  505. /**
  506. * Generates a legend from an array or object vector of legend item values. This method
  507. * simply instantiates a new <code>Legend</code> instance with the
  508. * given title and then invokes the <code>buildFromValues</code> method
  509. * with the given value array or object vector.
  510. * @param title the legend title text, or null for no title.
  511. * @param values an array or object vector of values for the legend items. See the
  512. * documentation for the <code>buildFromValues</code> method for more.
  513. * @return the generated legend
  514. */
  515. public static function fromValues(title:String, values:*):Legend
  516. {
  517. var legend:Legend = new Legend(title);
  518. legend.buildFromValues(values);
  519. legend.update();
  520. return legend;
  521. }
  522. } // end of class Legend
  523. }