    package flare.vis.operator.layout
    {
        import flare.scale.ScaleType;
        import flare.util.Property;
        import flare.vis.axis.CartesianAxes;
        import flare.vis.data.Data;
        import flare.vis.data.DataSprite;
        import flare.vis.data.ScaleBinding;

        /**
         * Layout that places items along the X and Y axes according to data
         * properties. The AxisLayout can also compute stacked layouts, in which
         * elements that share the same data values along an axis are consecutively
         * stacked on top of each other.
         */
        public class AxisLayout extends Layout
        {
            protected var _xStacks:Boolean = false;
            protected var _yStacks:Boolean = false;

            protected var _xField:Property;
            protected var _yField:Property;
            protected var _xBinding:ScaleBinding;
            protected var _yBinding:ScaleBinding;

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

            /** The x-axis source property. */
            public function get xField():String { return _xBinding.property; }
            public function set xField(f:String):void { _xBinding.property = f; }

            /** The y-axis source property. */
            public function get yField():String { return _yBinding.property; }
            public function set yField(f:String):void { _yBinding.property = f; }

            /** Flag indicating if values should be stacked according to their
             *  x-axis values. */
            public function get xStacked():Boolean { return _xStacks; }
            public function set xStacked(b:Boolean):void { _xStacks = b; }

            /** Flag indicating if values should be stacked according to their
             *  y-axis values. */
            public function get yStacked():Boolean { return _yStacks; }
            public function set yStacked(b:Boolean):void { _yStacks = b; }

            /** The scale binding for the x-axis. */
            public function get xScale():ScaleBinding { return _xBinding; }
            public function set xScale(b:ScaleBinding):void {
                if (_xBinding) {
                    if (!b.property) b.property = _xBinding.property;
                    if (!b.group) b.group = _xBinding.group;
                    if (!b.data) b.data = _xBinding.data;
                }
                _xBinding = b;
            }

            /** The scale binding for the y-axis. */
            public function get yScale():ScaleBinding { return _yBinding; }
            public function set yScale(b:ScaleBinding):void {
                if (_yBinding) {
                    if (!b.property) b.property = _yBinding.property;
                    if (!b.group) b.group = _yBinding.group;
                    if (!b.data) b.data = _yBinding.data;
                }
                _yBinding = b;
            }

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

            /**
             * Creates a new AxisLayout
             * @param xAxisField the x-axis source property
             * @param yAxisField the y-axis source property
             * @param xStacked indicates if values should be stacked according to
             *  their x-axis values
             * @param yStacked indicates if values should be stacked according to
             *  their y-axis values
             */
            public function AxisLayout(xAxisField:String=null, yAxisField:String=null,
                                       xStacked:Boolean=false, yStacked:Boolean=false)
            {
                layoutType = CARTESIAN;

                _xBinding = new ScaleBinding();
                _xBinding.group = Data.NODES;
                _xBinding.property = xAxisField;
                _xStacks = xStacked;

                _yBinding = new ScaleBinding();
                _yBinding.group = Data.NODES;
                _yBinding.property = yAxisField;
                _yStacks = yStacked;
            }

            /** @inheritDoc */
            public override function setup():void
            {
                if (visualization==null) return;
                _xBinding.data = visualization.data;
                _yBinding.data = visualization.data;

                var axes:CartesianAxes = super.xyAxes;
                axes.xAxis.axisScale = _xBinding;
                axes.yAxis.axisScale = _yBinding;
            }

            /** @inheritDoc */
            protected override function layout():void
            {
                _xField = Property.$(_xBinding.property);
                _yField = Property.$(_yBinding.property);

                var axes:CartesianAxes = super.xyAxes;
                _xBinding.updateBinding(); axes.xAxis.axisScale = _xBinding;
                _yBinding.updateBinding(); axes.yAxis.axisScale = _yBinding;

                if (_xStacks || _yStacks) { rescale(); }
                var x0:Number = axes.originX;
                var y0:Number = axes.originY;

                var xmapPos:Object = _xStacks ? new Object() : null;
                var xmapNeg:Object = _xStacks ? new Object() : null;
                var ymapPos:Object = _yStacks ? new Object() : null;
                var ymapNeg:Object = _yStacks ? new Object() : null;

                visualization.data.nodes.visit(function(d:DataSprite):void {
                    var dx:Object, dy:Object, x:Number, y:Number, s:Number, z:Number;
                    var o:Object = _t.$(d);
                    dx = _xField.getValue(d); dy = _yField.getValue(d);

                    var map:Object;
                    if (_xField != null) {
                        x = axes.xAxis.X(dx);
                        if (_xStacks) {
                            map = (dx < 0 ? xmapNeg : xmapPos);
                            z = x - x0;
                            s = z + (isNaN(s=map[dy]) ? 0 : s);
                            o.x = x0 + s;
                            o.w = z;
                            map[dy] = s;
                        } else {
                            o.x = x;
                            o.w = x - x0;
                        }
                    }
                    if (_yField != null) {
                        y = axes.yAxis.Y(dy);
                        if (_yStacks) {
                            map = (dy < 0 ? ymapNeg : ymapPos);
                            z = y - y0;
                            s = z + (isNaN(s=map[dx]) ? 0 : s);
                            o.y = y0 + s;
                            o.h = z;
                            map[dx] = s;
                        } else {
                            o.y = y;
                            o.h = y - y0;
                        }
                    }
                });
            }

            /** @private */
            protected function rescale():void {
                var xmapPos:Object = _xStacks ? new Object() : null;
                var xmapNeg:Object = _xStacks ? new Object() : null;
                var ymapPos:Object = _yStacks ? new Object() : null;
                var ymapNeg:Object = _yStacks ? new Object() : null;
                var xmax:Number = 0;
                var xmin:Number = 0;
                var ymax:Number = 0;
                var ymin:Number = 0;

                visualization.data.nodes.visit(function(d:DataSprite):void {
                    var x:Object = _xField.getValue(d);
                    var y:Object = _yField.getValue(d);
                    var v:Number;

                    if (_xStacks) {
                        if (x < 0) {
                            v = isNaN(xmapNeg[y]) ? 0 : xmapNeg[y];
                            xmapNeg[y] = v = (Number(x) + v);
                            if (v < xmin) xmin = v;
                        } else {
                            v = isNaN(xmapPos[y]) ? 0 : xmapPos[y];
                            xmapPos[y] = v = (Number(x) + v);
                            if (v > xmax) xmax = v;
                        }
                    }
                    if (_yStacks) {
                        if (y < 0) {
                            v = isNaN(ymapNeg[x]) ? 0 : ymapNeg[x];
                            ymapNeg[x] = v = (Number(y) + v);
                            if (v < ymin) ymin = v;

                        } else {
                            v = isNaN(ymapPos[x]) ? 0 : ymapPos[x];
                            ymapPos[x] = v = (Number(y) + v);
                            if (v > ymax) ymax = v;
                        }
                    }
                });

                if (_xStacks) {
                    _xBinding.scaleType = ScaleType.LINEAR;
                    _xBinding.preferredMin = xmin;
                    _xBinding.preferredMax = xmax;
                }
                if (_yStacks) {
                    _yBinding.scaleType = ScaleType.LINEAR;
                    _yBinding.preferredMin = ymin;
                    _yBinding.preferredMax = ymax;
                }
            }

        } // end of class AxisLayout
    }