PageRenderTime 192ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/vis/genvis/src/genvis/vis/Visualization.as

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