PageRenderTime 81ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/flare/src/flare/vis/Visualization.as

https://github.com/androtilde/Flare
ActionScript | 499 lines | 267 code | 53 blank | 179 comment | 61 complexity | 847c72722211108958718f8be84693f5 MD5 | raw file
  1. package flare.vis
  2. {
  3. import flare.animate.ISchedulable;
  4. import flare.animate.Scheduler;
  5. import flare.animate.TransitionEvent;
  6. import flare.animate.Transitioner;
  7. import flare.util.Displays;
  8. import flare.vis.axis.Axes;
  9. import flare.vis.axis.CartesianAxes;
  10. import flare.vis.controls.ControlList;
  11. import flare.vis.data.Data;
  12. import flare.vis.data.Tree;
  13. import flare.vis.events.DataEvent;
  14. import flare.vis.events.VisualizationEvent;
  15. import flare.vis.operator.IOperator;
  16. import flare.vis.operator.OperatorList;
  17. import flash.display.DisplayObject;
  18. import flash.display.Sprite;
  19. import flash.events.Event;
  20. import flash.geom.Rectangle;
  21. [Event(name="update", type="flare.vis.events.VisualizationEvent")]
  22. /**
  23. * The Visualization class represents an interactive data visualization.
  24. * A visualization instance consists of
  25. * <ul>
  26. * <li>A <code>Data</code> instance containing <code>DataSprite</code>
  27. * objects that visually represent individual data elements</li>
  28. * <li>An <code>OperatorList</code> of visualization operators that
  29. * determine visual encodings for position, color, size and other
  30. * properties.</li>
  31. * <li>A <code>ControlList</code> of interactive controls that enable
  32. * interaction with the visualized data.</li>
  33. * <li>An <code>Axes</code> instance for presenting axes for metric
  34. * data visualizations. Axes are often configuring automatically by
  35. * the visualization's operators.</li>
  36. * </ul>
  37. *
  38. * <p>Visual objects are added to the display list within the
  39. * <code>marks</code> property of the visualization, as the
  40. * <code>Data</code> object is not a <code>DisplayObjectContainer</code>.
  41. * </p>
  42. *
  43. * <p>All visual elements are contained within <code>layers</code> Sprite.
  44. * This includes the <code>axes</code>, <code>marks</code>, and
  45. * (optionally) <code>labels</code> layers. Clients who wish to add
  46. * additional layers to a visualization should add them directly to the
  47. * <code>layers</code> sprite. Just take care to maintain the desired order
  48. * of elements to avoid occlusion.</p>
  49. *
  50. * <p>To create a new Visualization, load in a data set, construct
  51. * a <code>Data</code> instance, and instantiate a new
  52. * <code>Visualization</code> with the input data. Then add the series
  53. * of desired operators to the <code>operators</code> property to
  54. * define the visual encodings.</p>
  55. *
  56. * @see flare.vis.operator
  57. */
  58. public class Visualization extends Sprite
  59. {
  60. // -- Properties ------------------------------------------------------
  61. private var _bounds:Rectangle = new Rectangle(0,0,500,500);
  62. private var _layers:Sprite; // sprite for all layers in visualization
  63. private var _marks:Sprite; // sprite for all visualized data items
  64. private var _labels:Sprite; // (optional) sprite for labels
  65. private var _axes:Axes; // (optional) axes, lines, and axis labels
  66. private var _data:Data; // data structure holding visualized data
  67. private var _dataItemsAdded:Array; // data sprites added since last update()
  68. private var _dataItemsRemoved:Array; // data sprites removed since last update()
  69. private var _ops:Object; // map of all named operators
  70. private var _operators:OperatorList; // the "main" operator list
  71. private var _controls:ControlList; // interactive controls
  72. private var _rec:ISchedulable; // for running continuous updates
  73. /** An object storing extra properties for the visualziation. */
  74. public var props:Object = {};
  75. /** The layout bounds of the visualization. This determines the layout
  76. * region for data elements. For example, with an axis layout, the
  77. * bounds determined the data layout region--this does not include
  78. * space used by axis labels. */
  79. public function get bounds():Rectangle { return _bounds; }
  80. public function set bounds(r:Rectangle):void {
  81. _bounds = r;
  82. // if (stage) stage.invalidate();
  83. }
  84. /** Container sprite holding each layer in the visualization. */
  85. public function get layers():Sprite { return _layers; }
  86. /** Sprite containing the <code>DataSprite</code> instances. */
  87. public function get marks():Sprite { return _marks; }
  88. /** Sprite containing a separate layer for labels. Null by default. */
  89. public function get labels():Sprite { return _labels; }
  90. public function set labels(l:Sprite):void {
  91. if (_labels != null)
  92. _layers.removeChild(_labels);
  93. _labels = l;
  94. if (_labels != null) {
  95. _labels.name = "_labels";
  96. _layers.addChildAt(_labels, _layers.getChildIndex(_marks)+1);
  97. }
  98. }
  99. /**
  100. * The axes for this visualization. May be null if no axes are needed.
  101. */
  102. public function get axes():Axes { return _axes; }
  103. public function set axes(a:Axes):void {
  104. if (_axes != null)
  105. _layers.removeChild(_axes);
  106. _axes = a;
  107. if (_axes != null) {
  108. _axes.visualization = this;
  109. _axes.name = "_axes";
  110. _layers.addChildAt(_axes, 0);
  111. }
  112. }
  113. /** The axes as an x-y <code>CartesianAxes</code> instance. Returns
  114. * null if <code>axes</code> is null or not a cartesian axes instance.
  115. */
  116. public function get xyAxes():CartesianAxes { return _axes as CartesianAxes; }
  117. /** The visual data elements in this visualization. */
  118. public function get data():Data { return _data; }
  119. /** Tree structure of visual data elements in this visualization.
  120. * Generates a spanning tree over a graph structure, if necessary. */
  121. public function get tree():Tree { return _data.tree; }
  122. public function set data(d:Data):void
  123. {
  124. if (_data != null) {
  125. _data.visit(_marks.removeChild);
  126. _data.removeEventListener(DataEvent.ADD, dataAdded);
  127. _data.removeEventListener(DataEvent.REMOVE, dataRemoved);
  128. }
  129. _data = d;
  130. if (_data != null) {
  131. _data.visit(_marks.addChild);
  132. _data.addEventListener(DataEvent.ADD, dataAdded);
  133. _data.addEventListener(DataEvent.REMOVE, dataRemoved);
  134. }
  135. }
  136. /** Data Sprites added since the last update(). */
  137. public function get dataItemsAdded():Array { return _dataItemsAdded; }
  138. /** Data Sprites removed since the last update(). */
  139. public function get dataItemsRemoved():Array { return _dataItemsRemoved; }
  140. /** The operator list for defining the visual encodings. */
  141. public function get operators():OperatorList { return _operators; }
  142. /** The control list containing interactive controls. */
  143. public function get controls():ControlList { return _controls; }
  144. /** Flag indicating if the visualization should update with every
  145. * frame. False by default. */
  146. public function get continuousUpdates():Boolean { return _rec != null; }
  147. public function set continuousUpdates(b:Boolean):void
  148. {
  149. if (b && _rec==null) {
  150. _rec = new Recurrence(this);
  151. Scheduler.instance.add(_rec);
  152. }
  153. else if (!b && _rec!=null) {
  154. Scheduler.instance.remove(_rec);
  155. _rec = null;
  156. }
  157. }
  158. // -- Methods ---------------------------------------------------------
  159. /**
  160. * Creates a new Visualization with the given data and axes.
  161. * @param data the <code>Data</code> instance containing the
  162. * <code>DataSprite</code> elements in this visualization.
  163. * @param axes the <code>Axes</code> to use with this visualization.
  164. * Null by default; layout operators may re-configure the axes.
  165. */
  166. public function Visualization(data:Data=null, axes:Axes=null) {
  167. addChild(_layers = new Sprite());
  168. _layers.name = "_layers";
  169. _layers.addChild(_marks = new Sprite());
  170. _marks.name = "_marks";
  171. if (data != null) this.data = data;
  172. if (axes != null) this.axes = axes;
  173. _operators = new OperatorList();
  174. _operators.visualization = this;
  175. _ops = { main:_operators };
  176. _controls = new ControlList();
  177. _controls.visualization = this;
  178. // Displays.addStageListener(this, Event.RENDER, setHitArea, false, int.MIN_VALUE+1);
  179. Displays.addStageListener(this, Event.ENTER_FRAME, setHitArea, false, int.MIN_VALUE+1);
  180. }
  181. /**
  182. * Update this visualization, re-calculating axis layout and running
  183. * the operator chain. The input transitioner is used to actually
  184. * perform value updates, enabling animated transitions. This method
  185. * also issues a <code>VisualizationEvent.UPDATE</code> event to any
  186. * registered listeners.
  187. * @param t a transitioner or time span for updating object values. If
  188. * the input is a transitioner, it will be used to store the updated
  189. * values. If the input is a number, a new Transitioner with duration
  190. * set to the input value will be used. The input is null by default,
  191. * in which case object values are updated immediately.
  192. * @param operators an optional list of named operators to run in the
  193. * update.
  194. * @return the transitioner used to store updated values.
  195. */
  196. public function update(t:*=null, ...operators):Transitioner
  197. {
  198. if (operators) {
  199. if (operators.length == 0) {
  200. operators = null;
  201. } else if (operators[0] is Array) {
  202. operators = operators[0].length > 0 ? operators[0] : null;
  203. }
  204. }
  205. var trans:Transitioner = Transitioner.instance(t);
  206. if (_axes != null) _axes.update(trans);
  207. if (operators) {
  208. for each (var name:String in operators) {
  209. if (_ops.hasOwnProperty(name))
  210. _ops[name].operate(trans);
  211. else
  212. throw new Error("Unknown operator: " + name);
  213. }
  214. } else {
  215. _operators.operate(trans);
  216. }
  217. if (_axes != null) _axes.update(trans);
  218. fireEvent(VisualizationEvent.UPDATE, trans, operators);
  219. trans.addEventListener(TransitionEvent.END, updateTransitionEnded);
  220. return trans;
  221. }
  222. /**
  223. * A function generator that can be used to invoke a visualization
  224. * update at a later time. This method returns a function that
  225. * accepts a <code>Transitioner</code> as its sole argument and then
  226. * executes a visualization update using the specified named
  227. * operators.
  228. * @param operators an optional array of named operators to run
  229. * @return a function that takes a <code>Transitioner</code> argument
  230. * and invokes an update.
  231. */
  232. public function updateLater(...operators):Function
  233. {
  234. return function(t:Transitioner):Transitioner {
  235. return update(t, operators);
  236. }
  237. }
  238. /**
  239. * Updates the data display bounds for a visualization based on a
  240. * given aspect ratio and provided width and height values. If both
  241. * width and height values are provided, they will be treated as the
  242. * maximum bounds. If only one of the width or height is provided, then
  243. * the width or height will match that value, and the other will be
  244. * determined by the aspect ratio. Finally, if neither width nor height
  245. * is provided, then the current width and height of the display bounds
  246. * will be used as the maximum bounds. After calling this method, a
  247. * call to <code>update</code> is necessary to reflect the change.
  248. * @param ar the desired aspect ratio for the data display
  249. * @param width the desired width. If a height value is also provided,
  250. * this width value will be treated as the maximum possible width
  251. * (the actual width may be lower).
  252. * @param height the desired height. If a width value is also provided,
  253. * this height value will be treated as the maximum possible height
  254. * (the actual height may be lower).
  255. */
  256. public function setAspectRatio(ar:Number, width:Number=-1,
  257. height:Number=-1):void
  258. {
  259. // compute new bounds
  260. if (width > 0 && height < 0) {
  261. height = width / ar;
  262. } else if (width < 0 && height > 0) {
  263. width = ar * height;
  264. } else {
  265. if (width < 0 && height < 0) {
  266. width = bounds.width;
  267. height = bounds.height;
  268. }
  269. if (ar > 1) { // width > height
  270. height = width / ar;
  271. } else if (ar < 1) { // height > width
  272. width = ar * height;
  273. }
  274. }
  275. // update bounds
  276. bounds.width = width;
  277. bounds.height = height;
  278. }
  279. // -- Named Operators -------------------------------------------------
  280. /**
  281. * Sets a new named operator. This method can be used to add extra
  282. * operators to a visualization, in addition to those in the main
  283. * <code>operators</code> property. These operators can be invoked by
  284. * passing the operator name as an additional parameter of the
  285. * <code>update</code> method. If an operator of the same name
  286. * already exists, it will be replaced. Note that the name "main"
  287. * refers to the same operator list as the <code>operators</code>
  288. * property and can not be replaced.
  289. * @param name the name of the operator to add
  290. * @param op the operator to add
  291. * @return the added operator
  292. */
  293. public function setOperator(name:String, op:IOperator):IOperator
  294. {
  295. if (name=="main") {
  296. throw new ArgumentError("Illegal group name: " +
  297. "\"main\" is a reserved name.");
  298. }
  299. _ops[name] = op;
  300. op.visualization = this;
  301. return op;
  302. }
  303. /**
  304. * Removes a named operator. An error will be thrown if the caller
  305. * attempts to remove the operator "main".
  306. * @param name the name of the operator to remove
  307. * @return the removed operator
  308. */
  309. public function removeOperator(name:String):IOperator
  310. {
  311. if (name=="main") {
  312. throw new ArgumentError("Illegal group name: " +
  313. "\"main\" is a reserved name.");
  314. }
  315. var op:IOperator = _ops[name];
  316. if (op) delete _ops[name];
  317. return op;
  318. }
  319. /**
  320. * Retrieves the operator with the given name. The name "main" will
  321. * return the operator list stored in the <code>operators</code>
  322. * property.
  323. * @param name the name of the operator
  324. * @return the operator
  325. */
  326. public function operator(name:String):IOperator
  327. {
  328. return _ops[name];
  329. }
  330. // -- Event Handling --------------------------------------------------
  331. /**
  332. * Creates a sprite covering the bounds for this visualization and
  333. * sets it to be this visualization's hit area. Typically, this
  334. * method is triggered in response to a <code>RENDER</code> event.
  335. * <p>To disable automatic hit area calculation, use
  336. * <code>stage.removeEventListener(Event.RENDER, vis.setHitArea)</code>
  337. * <em>after</em> the visualization has been added to the stage.</p>
  338. * @param evt an event that triggered the hit area update
  339. */
  340. public function setHitArea(evt:Event=null):void
  341. {
  342. // get the union of the specified and actual bounds
  343. var rb:Rectangle = getBounds(this);
  344. var x1:Number = rb.left, x2:Number = rb.right;
  345. var y1:Number = rb.top, y2:Number = rb.bottom;
  346. if (bounds) {
  347. x1 = Math.min(x1, bounds.left);
  348. y1 = Math.min(y1, bounds.top);
  349. x2 = Math.max(x2, bounds.right);
  350. y2 = Math.max(y1, bounds.bottom);
  351. }
  352. // create the hit area sprite
  353. var hit:Sprite = getChildByName("_hitArea") as Sprite;
  354. if (hit == null) {
  355. hit = new Sprite();
  356. hit.name = "_hitArea";
  357. addChildAt(hit, 0);
  358. }
  359. hit.visible = false;
  360. hit.mouseEnabled = false;
  361. hit.graphics.clear();
  362. hit.graphics.beginFill(0xffffff, 1);
  363. hit.graphics.drawRect(x1, y1, x2-x1, y2-y1);
  364. hit.graphics.endFill();
  365. hitArea = hit;
  366. }
  367. /**
  368. * Fires a visualization event of the given type.
  369. * @param type the type of the event
  370. * @param t a transitioner that listeners should use for any value
  371. * updates performed in response to this event
  372. */
  373. protected function fireEvent(type:String, t:Transitioner,
  374. params:Array):void
  375. {
  376. // fire event, if anyone is listening
  377. if (hasEventListener(type)) {
  378. dispatchEvent(new VisualizationEvent(type, t, params));
  379. }
  380. }
  381. /**
  382. * Transition listener invoked when update() transition has ended.
  383. * @param evt the transition event.
  384. */
  385. protected function updateTransitionEnded(evt:TransitionEvent):void
  386. {
  387. evt.target.removeEventListener(TransitionEvent.END, updateTransitionEnded);
  388. for each (var d:DisplayObject in _dataItemsRemoved) {
  389. if (d.parent == null) continue;
  390. _marks.removeChild(d);
  391. }
  392. _dataItemsAdded = null;
  393. _dataItemsRemoved = null;
  394. }
  395. /**
  396. * Data listener invoked when new items are added to this
  397. * Visualization's <code>data</code> instance.
  398. * @param evt the data event
  399. */
  400. protected function dataAdded(evt:DataEvent):void
  401. {
  402. _dataItemsAdded ||= new Array();
  403. if (evt.node) {
  404. for each (var d:DisplayObject in evt.items) {
  405. _marks.addChild(d);
  406. _dataItemsAdded.push(d);
  407. }
  408. } else {
  409. for each (d in evt.items) {
  410. _marks.addChildAt(d, 0);
  411. _dataItemsAdded.push(d);
  412. }
  413. }
  414. }
  415. /**
  416. * Data listener invoked when new items are removed from this
  417. * Visualization's <code>data</code> instance.
  418. * @param evt the data event
  419. */
  420. protected function dataRemoved(evt:DataEvent):void
  421. {
  422. _dataItemsRemoved ||= new Array();
  423. for each (var d:DisplayObject in evt.items) {
  424. _dataItemsRemoved.push(d);
  425. }
  426. }
  427. } // end of class Visualization
  428. }
  429. import flare.animate.ISchedulable;
  430. import flare.vis.Visualization;
  431. /**
  432. * Simple ISchedulable instance that repeatedly calls a Visualization's
  433. * <code>update</code> method.
  434. */
  435. class Recurrence implements ISchedulable {
  436. private var _vis:Visualization;
  437. public function get id():String { return null; }
  438. public function set id(s:String):void { /* do nothing */ }
  439. public function cancelled():void { /* do nothing */ }
  440. public function Recurrence(vis:Visualization) {
  441. _vis = vis;
  442. }
  443. public function evaluate(t:Number):Boolean {
  444. _vis.update(); return false;
  445. }
  446. }