package flare.vis.operator.layout { import flare.util.Property; import flare.vis.data.Data; import flare.vis.data.DataList; import flare.vis.data.NodeSprite; import flare.vis.data.ScaleBinding; import flash.geom.Point; import flash.geom.Rectangle; /** * Layout that places items in a circular layout. This operator is quite * flexible and offers a number of layout options: *
treeLayout
* property is set to true
, the layout will use an
* underlying tree structure to layout the data. Leaf nodes will be
* placed along the circumference of the circle, but parent nodes will
* be placed in the interior. Also, the layout will add spacing to
* differentiate sibling groups along the circumference.The layout also supports mixes of the above modes. For example, if
* treeLayout
is set to true
and a data field for
* the radius is set, the angles in the layout will be determined as in
* a normal ciruclar tree layout, but the radius values will be derived
* using the data field.
startRadiusFraction
property. */
public function get startRadius():Number { return _inner; }
public function set startRadius(r:Number):void {
_inner = r; _innerFrac = NaN;
}
/** The starting (inner) radius as a fraction of the outer radius.
* Setting this value also overrides the
* startRadius
property. When this property is set to
* NaN
, the current value of startRadius
* will be used directly. */
public function get startRadiusFraction():Number { return _innerFrac; }
public function set startRadiusFraction(f:Number):void {
_innerFrac = f;
}
/** The radius source property. */
public function get radiusField():String { return _rBinding.property; }
public function set radiusField(f:String):void { _rBinding.property = f; }
/** The angle source property. */
public function get angleField():String { return _aBinding.property; }
public function set angleField(f:String):void { _aBinding.property = f; }
/** The scale binding for the radius. */
public function get radiusScale():ScaleBinding { return _rBinding; }
public function set radiusScale(b:ScaleBinding):void {
if (_rBinding) {
if (!b.property) b.property = _rBinding.property;
if (!b.group) b.group = _rBinding.group;
if (!b.data) b.data = _rBinding.data;
}
_rBinding = b;
}
/** The scale binding for the angle. */
public function get angleScale():ScaleBinding { return _aBinding; }
public function set angleScale(b:ScaleBinding):void {
if (_aBinding) {
if (!b.property) b.property = _aBinding.property;
if (!b.group) b.group = _aBinding.group;
if (!b.data) b.data = _aBinding.data;
}
_aBinding = b;
}
// --------------------------------------------------------------------
/**
* Creates a new CircleLayout.
* @param radiusField optional data field to encode as radius length
* @param angleField optional data field to encode as angle
* @param treeLayout boolean flag indicating if any tree-structure in
* the data should be used to inform the layout
* @param group the data group to process. If tree layout is set to
* true, this value may get ignored.
*/
public function CircleLayout(
radiusField:String=null, angleField:String=null,
treeLayout:Boolean=false, group:String=Data.NODES)
{
layoutType = POLAR;
_group = group;
this.treeLayout = treeLayout;
_rBinding = new ScaleBinding();
_rBinding.group = _group;
_rBinding.property = radiusField;
_aBinding = new ScaleBinding();
_aBinding.group = _group;
_aBinding.property = angleField;
}
/** @inheritDoc */
public override function setup():void
{
if (visualization==null) return;
_rBinding.data = visualization.data;
_aBinding.data = visualization.data;
}
/** @inheritDoc */
protected override function layout():void
{
var list:DataList = visualization.data.group(_group);
var i:int = 0, N:int = list.length, dr:Number;
var visitor:Function = null;
// determine radius
var b:Rectangle = layoutBounds;
_outer = Math.min(b.width, b.height)/2 - padding;
_inner = isNaN(_innerFrac) ? _inner : _outer * _innerFrac;
// set the anchor point
var anchor:Point = layoutAnchor;
list.visit(function(n:NodeSprite):void { n.origin = anchor; });
// compute angles
if (_aBinding.property) {
// if angle property, get scale binding and do layout
_aBinding.updateBinding();
_aField = Property.$(_aBinding.property);
visitor = function(n:NodeSprite):void {
var f:Number = _aBinding.interpolate(_aField.getValue(n));
_t.$(n).angle = minAngle(n.angle,
startAngle - f*angleWidth);
};
} else if (treeLayout) {
// if tree mode, use tree order
setTreeAngles();
} else {
// if nothing use total sort order
i = 0;
visitor = function(n:NodeSprite):void {
_t.$(n).angle = minAngle(n.angle,
startAngle - (i/N)*angleWidth);
i++;
};
}
if (visitor != null) list.visit(visitor);
// compute radii
visitor = null;
if (_rBinding.property) {
// if radius property, get scale binding and do layout
_rBinding.updateBinding();
_rField = Property.$(_rBinding.property);
dr = _outer - _inner;
visitor = function(n:NodeSprite):void {
var f:Number = _rBinding.interpolate(_rField.getValue(n));
_t.$(n).radius = _inner + f * dr;
};
} else if (treeLayout) {
// if tree-mode, use tree depth
setTreeRadii();
} else {
// if nothing, use outer radius
visitor = function(n:NodeSprite):void {
_t.$(n).radius = _outer;
};
}
if (visitor != null) list.visit(visitor);
if (treeLayout) _t.$(visualization.data.tree.root).radius = 0;
// finish up
updateEdgePoints(_t);
}
private function setTreeAngles():void
{
// first pass, determine the angular spacing
var root:NodeSprite = visualization.tree.root, p:NodeSprite = null;
var leafCount:int = 0, parentCount:int = 0;
root.visitTreeDepthFirst(function(n:NodeSprite):void {
if (n.childDegree == 0) {
if (p != n.parentNode) {
p = n.parentNode;
++parentCount;
}
++leafCount;
}
});
var inc:Number = (-angleWidth) / (leafCount + parentCount);
var angle:Number = startAngle;
// second pass, set the angles
root.visitTreeDepthFirst(function(n:NodeSprite):void {
var a:Number = 0, b:Number;
if (n.childDegree == 0) {
if (p != n.parentNode) {
p = n.parentNode;
angle += inc;
}
a = angle;
angle += inc;
} else if (n.parent != null) {
a = _t.$(n.firstChildNode).angle;
b = _t.$(n.lastChildNode).angle - a;
while (b > Math.PI) b -= 2*Math.PI;
while (b < -Math.PI) b += 2*Math.PI;
a += b / 2;
}
_t.$(n).angle = minAngle(n.angle, a);
});
}
private function setTreeRadii():void
{
var n:NodeSprite;
var depth:Number = 0, dr:Number = _outer - _inner;
for each (n in visualization.tree.nodes) {
if (n.childDegree == 0) {
depth = Math.max(n.depth, depth);
_t.$(n).radius = _outer;
}
}
for each (n in visualization.tree.nodes) {
if (n.childDegree != 0) {
_t.$(n).radius = _inner + (n.depth/depth) * dr;
}
}
n = visualization.tree.root;
if (!_t.immediate) {
delete _t._(n).values.radius;
delete _t._(n).values.angle;
}
_t.$(n).x = n.origin.x;
_t.$(n).y = n.origin.y;
}
} // end of class CircleLayout
}