PageRenderTime 26ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/flare/src/flare/vis/operator/layout/CircleLayout.as

http://github.com/johnyanarella/flare
ActionScript | 270 lines | 180 code | 26 blank | 64 comment | 42 complexity | 6db8ca0c652c6f85e1a606d47fef0b7e MD5 | raw file
  1. package flare.vis.operator.layout
  2. {
  3. import flare.util.Property;
  4. import flare.vis.data.Data;
  5. import flare.vis.data.DataList;
  6. import flare.vis.data.NodeSprite;
  7. import flare.vis.data.ScaleBinding;
  8. import flash.geom.Point;
  9. import flash.geom.Rectangle;
  10. /**
  11. * Layout that places items in a circular layout. This operator is quite
  12. * flexible and offers a number of layout options:
  13. * <ul>
  14. * <li>By default, all items are arranged along the circumference of the
  15. * circle using the sort order of the underlying data list.</li>
  16. * <li>If a data field for either the radius or angle is provided, this
  17. * layout will act as a radial scatter plot, using the data fields
  18. * to determine the radius or angle values of the layout.</li>
  19. * <li>If no data field is provided but the <code>treeLayout</code>
  20. * property is set to <code>true</code>, the layout will use an
  21. * underlying tree structure to layout the data. Leaf nodes will be
  22. * placed along the circumference of the circle, but parent nodes will
  23. * be placed in the interior. Also, the layout will add spacing to
  24. * differentiate sibling groups along the circumference.</li>
  25. * </ul>
  26. *
  27. * <p>The layout also supports mixes of the above modes. For example, if
  28. * <code>treeLayout</code> is set to <code>true</code> and a data field for
  29. * the radius is set, the angles in the layout will be determined as in
  30. * a normal ciruclar tree layout, but the radius values will be derived
  31. * using the data field.</p>
  32. */
  33. public class CircleLayout extends Layout
  34. {
  35. /** The padding around the circumference of the circle, in pixels. */
  36. public var padding:Number = 50;
  37. /** The starting angle for the layout, in radians. */
  38. public var startAngle:Number = Math.PI / 2;
  39. /** The angular width of the layout, in radians (default is 2 pi). */
  40. public var angleWidth:Number = 2 * Math.PI;
  41. /** Flag indicating if tree structure should inform the layout. */
  42. public var treeLayout:Boolean = false;
  43. protected var _inner:Number = 0, _innerFrac:Number = NaN;
  44. protected var _outer:Number;
  45. protected var _group:String;
  46. protected var _rField:Property;
  47. protected var _aField:Property;
  48. protected var _rBinding:ScaleBinding;
  49. protected var _aBinding:ScaleBinding;
  50. /** The starting (inner) radius at which to place items.
  51. * Setting this value also overrides the
  52. * <code>startRadiusFraction</code> property. */
  53. public function get startRadius():Number { return _inner; }
  54. public function set startRadius(r:Number):void {
  55. _inner = r; _innerFrac = NaN;
  56. }
  57. /** The starting (inner) radius as a fraction of the outer radius.
  58. * Setting this value also overrides the
  59. * <code>startRadius</code> property. When this property is set to
  60. * <code>NaN</code>, the current value of <code>startRadius</code>
  61. * will be used directly. */
  62. public function get startRadiusFraction():Number { return _innerFrac; }
  63. public function set startRadiusFraction(f:Number):void {
  64. _innerFrac = f;
  65. }
  66. /** The radius source property. */
  67. public function get radiusField():String { return _rBinding.property; }
  68. public function set radiusField(f:String):void { _rBinding.property = f; }
  69. /** The angle source property. */
  70. public function get angleField():String { return _aBinding.property; }
  71. public function set angleField(f:String):void { _aBinding.property = f; }
  72. /** The scale binding for the radius. */
  73. public function get radiusScale():ScaleBinding { return _rBinding; }
  74. public function set radiusScale(b:ScaleBinding):void {
  75. if (_rBinding) {
  76. if (!b.property) b.property = _rBinding.property;
  77. if (!b.group) b.group = _rBinding.group;
  78. if (!b.data) b.data = _rBinding.data;
  79. }
  80. _rBinding = b;
  81. }
  82. /** The scale binding for the angle. */
  83. public function get angleScale():ScaleBinding { return _aBinding; }
  84. public function set angleScale(b:ScaleBinding):void {
  85. if (_aBinding) {
  86. if (!b.property) b.property = _aBinding.property;
  87. if (!b.group) b.group = _aBinding.group;
  88. if (!b.data) b.data = _aBinding.data;
  89. }
  90. _aBinding = b;
  91. }
  92. // --------------------------------------------------------------------
  93. /**
  94. * Creates a new CircleLayout.
  95. * @param radiusField optional data field to encode as radius length
  96. * @param angleField optional data field to encode as angle
  97. * @param treeLayout boolean flag indicating if any tree-structure in
  98. * the data should be used to inform the layout
  99. * @param group the data group to process. If tree layout is set to
  100. * true, this value may get ignored.
  101. */
  102. public function CircleLayout(
  103. radiusField:String=null, angleField:String=null,
  104. treeLayout:Boolean=false, group:String=Data.NODES)
  105. {
  106. layoutType = POLAR;
  107. _group = group;
  108. this.treeLayout = treeLayout;
  109. _rBinding = new ScaleBinding();
  110. _rBinding.group = _group;
  111. _rBinding.property = radiusField;
  112. _aBinding = new ScaleBinding();
  113. _aBinding.group = _group;
  114. _aBinding.property = angleField;
  115. }
  116. /** @inheritDoc */
  117. public override function setup():void
  118. {
  119. if (visualization==null) return;
  120. _rBinding.data = visualization.data;
  121. _aBinding.data = visualization.data;
  122. }
  123. /** @inheritDoc */
  124. protected override function layout():void
  125. {
  126. var list:DataList = visualization.data.group(_group);
  127. var i:int = 0, N:int = list.length, dr:Number;
  128. var visitor:Function = null;
  129. // determine radius
  130. var b:Rectangle = layoutBounds;
  131. _outer = Math.min(b.width, b.height)/2 - padding;
  132. _inner = isNaN(_innerFrac) ? _inner : _outer * _innerFrac;
  133. // set the anchor point
  134. var anchor:Point = layoutAnchor;
  135. list.visit(function(n:NodeSprite):void { n.origin = anchor; });
  136. // compute angles
  137. if (_aBinding.property) {
  138. // if angle property, get scale binding and do layout
  139. _aBinding.updateBinding();
  140. _aField = Property.$(_aBinding.property);
  141. visitor = function(n:NodeSprite):void {
  142. var f:Number = _aBinding.interpolate(_aField.getValue(n));
  143. _t.$(n).angle = minAngle(n.angle,
  144. startAngle - f*angleWidth);
  145. };
  146. } else if (treeLayout) {
  147. // if tree mode, use tree order
  148. setTreeAngles();
  149. } else {
  150. // if nothing use total sort order
  151. i = 0;
  152. visitor = function(n:NodeSprite):void {
  153. _t.$(n).angle = minAngle(n.angle,
  154. startAngle - (i/N)*angleWidth);
  155. i++;
  156. };
  157. }
  158. if (visitor != null) list.visit(visitor);
  159. // compute radii
  160. visitor = null;
  161. if (_rBinding.property) {
  162. // if radius property, get scale binding and do layout
  163. _rBinding.updateBinding();
  164. _rField = Property.$(_rBinding.property);
  165. dr = _outer - _inner;
  166. visitor = function(n:NodeSprite):void {
  167. var f:Number = _rBinding.interpolate(_rField.getValue(n));
  168. _t.$(n).radius = _inner + f * dr;
  169. };
  170. } else if (treeLayout) {
  171. // if tree-mode, use tree depth
  172. setTreeRadii();
  173. } else {
  174. // if nothing, use outer radius
  175. visitor = function(n:NodeSprite):void {
  176. _t.$(n).radius = _outer;
  177. };
  178. }
  179. if (visitor != null) list.visit(visitor);
  180. if (treeLayout) _t.$(visualization.data.tree.root).radius = 0;
  181. // finish up
  182. updateEdgePoints(_t);
  183. }
  184. private function setTreeAngles():void
  185. {
  186. // first pass, determine the angular spacing
  187. var root:NodeSprite = visualization.tree.root, p:NodeSprite = null;
  188. var leafCount:int = 0, parentCount:int = 0;
  189. root.visitTreeDepthFirst(function(n:NodeSprite):void {
  190. if (n.childDegree == 0) {
  191. if (p != n.parentNode) {
  192. p = n.parentNode;
  193. ++parentCount;
  194. }
  195. ++leafCount;
  196. }
  197. });
  198. var inc:Number = (-angleWidth) / (leafCount + parentCount);
  199. var angle:Number = startAngle;
  200. // second pass, set the angles
  201. root.visitTreeDepthFirst(function(n:NodeSprite):void {
  202. var a:Number = 0, b:Number;
  203. if (n.childDegree == 0) {
  204. if (p != n.parentNode) {
  205. p = n.parentNode;
  206. angle += inc;
  207. }
  208. a = angle;
  209. angle += inc;
  210. } else if (n.parent != null) {
  211. a = _t.$(n.firstChildNode).angle;
  212. b = _t.$(n.lastChildNode).angle - a;
  213. while (b > Math.PI) b -= 2*Math.PI;
  214. while (b < -Math.PI) b += 2*Math.PI;
  215. a += b / 2;
  216. }
  217. _t.$(n).angle = minAngle(n.angle, a);
  218. });
  219. }
  220. private function setTreeRadii():void
  221. {
  222. var n:NodeSprite;
  223. var depth:Number = 0, dr:Number = _outer - _inner;
  224. for each (n in visualization.tree.nodes) {
  225. if (n.childDegree == 0) {
  226. depth = Math.max(n.depth, depth);
  227. _t.$(n).radius = _outer;
  228. }
  229. }
  230. for each (n in visualization.tree.nodes) {
  231. if (n.childDegree != 0) {
  232. _t.$(n).radius = _inner + (n.depth/depth) * dr;
  233. }
  234. }
  235. n = visualization.tree.root;
  236. if (!_t.immediate) {
  237. delete _t._(n).values.radius;
  238. delete _t._(n).values.angle;
  239. }
  240. _t.$(n).x = n.origin.x;
  241. _t.$(n).y = n.origin.y;
  242. }
  243. } // end of class CircleLayout
  244. }