    package flare.vis.operator.layout
    {
        import flare.util.Orientation;
        import flare.vis.data.NodeSprite;

        import flash.geom.Rectangle;

        /**
         * Layout that places nodes in an icicle layout, distributing nodes
         * evenly within the display bounds. To get a standard icicle view, set
         * the nodes <code>shape</code> property to <code>Shapes.BLOCK</code> and
         * hide all edges. By default, this operator will attempt to scale the
         * layout to fit within the display bounds. By setting
         * <code>fitToBounds</code> false, the current <code>depthSpacing<code>
         * will be preserved, allowing the layout to exceed the bounds along
         * the depth dimension.
         */
        public class IcicleTreeLayout extends Layout
        {
            private var _orient:String = Orientation.TOP_TO_BOTTOM; // orientation
            private var _dspace:Number = 50; // the spacing between depth levels
            private var _maxDepth:int = 0;
            private var _vertical:Boolean = true;

            /** Indicates if the layout should be scaled to fit in the bounds. */
            public var fitToBounds:Boolean = true;

            /** The orientation of the layout. */
            public function get orientation():String { return _orient; }
            public function set orientation(o:String):void { _orient = o; }

            /** The space between successive depth levels of the tree. */
            public function get depthSpacing():Number { return _dspace; }
            public function set depthSpacing(s:Number):void { _dspace = s; }

            // --------------------------------------------------------------------

            /**
             * Creates a new IcicleTreeLayout.
             * @param orientation the orientation of the layout
             */
            public function IcicleTreeLayout(orientation:String=null)
            {
                if (orientation) this.orientation = orientation;
            }

            /** @inheritDoc */
            protected override function layout():void
            {
                // get bounds parameters
                var root:NodeSprite = layoutRoot as NodeSprite;
                var b:Rectangle = layoutBounds;
                var bMin:Number, bMax:Number, dMax:Number, d:Number, dInc:Number;
                switch (_orient) {
                    case Orientation.LEFT_TO_RIGHT:
                    case Orientation.RIGHT_TO_LEFT:
                        bMin = b.bottom;
                        bMax = b.top;
                        dMax = b.width;
                        _vertical = false;
                        break;
                    case Orientation.TOP_TO_BOTTOM:
                    case Orientation.BOTTOM_TO_TOP:
                        bMin = b.left;
                        bMax = b.right;
                        dMax = b.height;
                        _vertical = true;
                        break;
                    default:
                        throw new Error("Unrecognized orientation value");
                }
                switch (_orient) {
                    case Orientation.LEFT_TO_RIGHT:
                        d = b.left; dInc = _dspace; break;
                    case Orientation.TOP_TO_BOTTOM:
                        d = b.top; dInc = _dspace; break;
                    case Orientation.RIGHT_TO_LEFT:
                        d = b.right; dInc = -_dspace; break;
                    case Orientation.BOTTOM_TO_TOP:
                        d = b.bottom; dInc = -_dspace; break;
                    default:
                        throw new Error("Unrecognized orientation value");
                }

                // calculate depth and width
                _maxDepth = 0;
                firstPass(root, 0);

                // scale the depth to fit as needed
                if (fitToBounds && _maxDepth * _dspace > dMax) {
                    dInc *= dMax / (_maxDepth * _dspace);
                }

                // perform the layout
                doLayout(root, bMin, bMax, d, dInc);
                updateEdgePoints(_t);
            }

            private function firstPass(n:NodeSprite, d:int):Number
            {
                if (d > _maxDepth) _maxDepth = d;
                var extent:Number = 0;
                if (n.childDegree == 0 || !n.expanded) {
                    extent = 1;
                } else {
                    for (var i:int=0; i<n.childDegree; ++i) {
                        extent += firstPass(n.getChildNode(i), d+1)
                    }
                }
                n.props.icicleWidth = extent;
                return extent;
            }

            private function doLayout(n:NodeSprite, b1:Number, b2:Number,
                d:Number, dInc:Number):void
            {
                var pw:Number = n.props.icicleWidth;
                var x:Number = b1, w:Number = b2 - b1;

                if (n.childDegree > 0 && !n.expanded) {
                    n.visitTreeDepthFirst(function(c:NodeSprite):void {
                        update(c, b1 + w/2, b1 + w/2, d+dInc, dInc, false);
                    });
                } else {
                    for (var i:int=0; i<n.childDegree; ++i) {
                        var c:NodeSprite = n.getChildNode(i);
                        var cw:Number = w * c.props.icicleWidth / pw;
                        doLayout(c, x, x+cw, d+dInc, dInc);
                        x += cw;
                    }
                }
                update(n, b1, b2, d, dInc, true);
            }

            private function update(n:NodeSprite, b1:Number, b2:Number,
                d:Number, dInc:Number, visible:Boolean):void
            {
                var o:Object = _t.$(n);
                if (_vertical) {
                    o.x = (b1 + b2) / 2;
                    o.y = d + dInc / 2;
                    o.u = b1;
                    o.v = d;
                    o.w = b2 - b1;
                    o.h = dInc;
                } else {
                    o.x = d + dInc / 2;
                    o.y = (b1 + b2) / 2;
                    o.u = d;
                    o.v = b1;
                    o.w = dInc;
                    o.h = b2 - b1;
                }
                var alpha:Number = visible ? 1 : 0;
                o.alpha = alpha;
                o.mouseEnabled = visible;
                if (n.parentEdge != null)
                    _t.$(n.parentEdge).alpha = alpha;
            }

        } // end of class IcicleTreeLayout
    }