package flare.vis.operator.layout {
import flare.util.Property;
import flare.util.Sort;
import flare.vis.data.NodeSprite;
import flash.geom.Rectangle;
/**
* Layout that places node in a TreeMap layout that optimizes for low
* aspect ratios of visualized tree nodes. TreeMaps are a form of
* space-filling layout that represents nodes as boxes on the display, with
* children nodes represented as boxes placed within their parent's box.
* This layout determines the area of nodes in the tree map by looking up
* the sizeField
property on leaf nodes. By default, this
* property is "size", such that the layout will look for size
* values in the DataSprite.size
property.
*
*
* This particular algorithm is taken from Bruls, D.M., C. Huizing, and
* J.J. van Wijk, "Squarified Treemaps" In Data Visualization 2000,
* Proceedings of the Joint Eurographics and IEEE TCVG Sumposium on
* Visualization , 2000, pp. 33-42. Available online at:
*
* http://www.win.tue.nl/~vanwijk/stm.pdf .
*
*
* For more information on TreeMaps in general, see
*
* http://www.cs.umd.edu/hcil/treemap-history/ .
*
*/
public class TreeMapLayout extends Layout
{
private static const AREA:String = "treeMapArea";
private var _kids:Vector. = new Vector.();
private var _row:Vector. = new Vector.();
private var _r:Rectangle = new Rectangle();
private var _size:Property = Property.$("size");
/** The property from which to access size values for leaf nodes. */
public function get sizeField():String { return _size.name; }
public function set sizeField(s:String):void { _size = Property.$(s); }
// --------------------------------------------------------------------
/**
* Creates a new TreeMapLayout
* @param sizeField the data property from which to access the size
* value for leaf nodes. The default is the "size" property.
*/
public function TreeMapLayout(sizeField:String="size") {
this.sizeField = sizeField;
}
/** @inheritDoc */
protected override function layout():void
{
// setup
var root:NodeSprite = layoutRoot as NodeSprite;
var b:Rectangle = layoutBounds;
_r.x=b.x; _r.y=b.y; _r.width=b.width-1; _r.height=b.height-1;
// process size values
computeAreas(root);
// layout root node
var o:Object = _t.$(root);
o.x = 0;//_r.x + _r.width/2;
o.y = 0;//_r.y + _r.height/2;
o.u = _r.x;
o.v = _r.y;
o.w = _r.width;
o.h = _r.height;
// layout the tree
updateArea(root, _r);
doLayout(root, _r);
}
/**
* Compute the pixel areas of nodes based on their size values.
*/
private function computeAreas(root:NodeSprite):void
{
var leafCount:int = 0;
// reset all sizes to zero
root.visitTreeDepthFirst(function(n:NodeSprite):void {
n.props[AREA] = 0;
});
// set raw sizes, compute leaf count
root.visitTreeDepthFirst(function(n:NodeSprite):void {
if (n.childDegree == 0) {
var sz:Number = _size.getValue(_t.$(n));
n.props[AREA] = sz;
var p:NodeSprite = n.parentNode;
for (; p != null; p=p.parentNode)
p.props[AREA] += sz;
++leafCount;
}
});
// scale sizes by display area factor
var b:Rectangle = layoutBounds;
var area:Number = (b.width-1)*(b.height-1);
var scale:Number = area / root.props[AREA];
root.visitTreeDepthFirst(function(n:NodeSprite):void {
n.props[AREA] *= scale;
});
}
/**
* Compute the tree map layout.
*/
private function doLayout(p:NodeSprite, r:Rectangle):void
{
// create sorted list of children's properties
for (var i:uint = 0; i < p.childDegree; ++i) {
_kids.push(p.getChildNode(i).props);
}
//_kids.sortOn(AREA, Array.NUMERIC);
_kids.sort(Sort.$(AREA, Array.NUMERIC));
// update array to point to sprites, not props
for (i = 0; i < _kids.length; ++i) {
_kids[i] = _kids[i].self;
}
// do squarified layout of siblings
var w:Number = Math.min(r.width, r.height);
squarify(_kids, _row, w, r);
_kids.splice(0, _kids.length); // clear _kids
// recurse
for (i=0; i 0) {
updateArea(c, r);
doLayout(c, r);
}
}
}
private function updateArea(n:NodeSprite, r:Rectangle):void
{
var o:Object = _t.$(n);
r.x = o.u;
r.y = o.v;
r.width = o.w;
r.height = o.h;
return;
/*
Rectangle2D b = n.getBounds();
if ( m_frame == 0.0 ) {
// if no framing, simply update bounding rectangle
r.setRect(b);
return;
}
// compute area loss due to frame
double dA = 2*m_frame*(b.getWidth()+b.getHeight()-2*m_frame);
double A = n.getDouble(AREA) - dA;
// compute renormalization factor
double s = 0;
Iterator childIter = n.children();
while ( childIter.hasNext() )
s += ((NodeItem)childIter.next()).getDouble(AREA);
double t = A/s;
// re-normalize children areas
childIter = n.children();
while ( childIter.hasNext() ) {
NodeItem c = (NodeItem)childIter.next();
c.setDouble(AREA, c.getDouble(AREA)*t);
}
// set bounding rectangle and return
r.setRect(b.getX()+m_frame, b.getY()+m_frame,
b.getWidth()-2*m_frame, b.getHeight()-2*m_frame);
return;
*/
}
private function squarify(c:Vector., row:Vector., w:Number, r:Rectangle):void
{
var worst:Number = Number.MAX_VALUE, nworst:Number;
var len:int;
while ((len=c.length) > 0) {
// add item to the row list, ignore if negative area
var item:NodeSprite = c[len-1] as NodeSprite;
var a:Number = item.props[AREA];
if (a <= 0.0) {
c.pop();
continue;
}
row.push(item);
nworst = getWorst(row, w);
if (nworst <= worst) {
c.pop();
worst = nworst;
} else {
row.pop(); // remove the latest addition
r = layoutRow(row, w, r); // layout the current row
w = Math.min(r.width, r.height); // recompute w
row.splice(0, row.length); // clear the row
worst = Number.MAX_VALUE;
}
}
if (row.length > 0) {
r = layoutRow(row, w, r); // layout the current row
row.splice(0, row.length); // clear the row
}
}
private function getWorst(rlist:Vector., w:Number):Number
{
var rmax:Number = Number.MIN_VALUE;
var rmin:Number = Number.MAX_VALUE;
var s:Number = 0;
for each (var n:NodeSprite in rlist) {
var r:Number = n.props[AREA];
rmin = Math.min(rmin, r);
rmax = Math.max(rmax, r);
s += r;
}
s = s*s; w = w*w;
return Math.max(w*rmax/s, s/(w*rmin));
}
private function layoutRow(row:Vector., ww:Number, r:Rectangle):Rectangle
{
var s:Number = 0; // sum of row areas
for each (var n:NodeSprite in row) {
s += n.props[AREA];
}
var xx:Number = r.x, yy:Number = r.y, d:Number = 0;
var hh:Number = ww==0 ? 0 : s/ww;
var horiz:Boolean = (ww == r.width);
// set node positions and dimensions
for each (n in row) {
var p:NodeSprite = n.parentNode;
var nw:Number = n.props[AREA]/hh;
var o:Object = _t.$(n);
if (horiz) {
o.u = xx + d;
o.v = yy;
o.w = nw;
o.h = hh;
//o.x = xx + d + nw/2;
//o.y = yy + hh/2;
} else {
o.u = xx;
o.v = yy + d;
o.w = hh;
o.h = nw;
//o.x = xx + hh/2;
//o.y = yy + d + nw/2;
}
o.x = 0;
o.y = 0;
d += nw;
}
// update space available in rectangle r
if (horiz) {
r.x = xx; r.y = yy+hh; r.height -= hh;
} else {
r.x = xx+hh; r.y = yy; r.width -= hh;
}
return r;
}
}
}