    package flare.vis.operator.distortion
    {
        import flare.animate.Transitioner;
        import flare.vis.axis.Axis;
        import flare.vis.axis.CartesianAxes;
        import flare.vis.data.Data;
        import flare.vis.data.DataSprite;
        import flare.vis.events.VisualizationEvent;
        import flare.vis.operator.layout.Layout;

        import flash.display.Sprite;
        import flash.geom.Point;
        import flash.geom.Rectangle;

        /**
         * Base class for distortion operators that adjust the position and size
         * of visual objects in a visualization.
         */
        public class Distortion extends Layout
        {
            /** A bounding rectangle for storing the layout bounds. */
            protected var _b:Rectangle;

            /** Flag indicating if x-coordinates should be distorted. */
            public var distortX:Boolean;
            /** Flag indicating if y-coordinates should be distorted. */
            public var distortY:Boolean;
            /** Flag indicating if sizes should be distorted. */
            public var distortSize:Boolean;
            /** Flag indicating if axes should be distorted. */
            public var distortAxes:Boolean;
            /** Flag indicating if the <code>DataSprite.size</code> field should be
             *  distorted. If false (the default), the scaleX and scaleY properties
             *  are used instead. */
            public var useSizeField:Boolean = false;
            /** Flag indicating if the size or scale values should be reset to 1
             *  upon each invocation of the distortion. This avoids the need to
             *  manually reset the size values on each update. The default value
             *  is false. */
            public var resetSize:Boolean = false;
            /** Flag indicating if distortion anchor points outside the layout
             *  bounds should be considered by the distortion. If true, external
             *  anchors will be mapped to nearest point on the border of the layout
             *  bounds. */
            public var anchorInBounds:Boolean = true;

            /** @inheritDoc */
            public override function set layoutAnchor(p:Point):void {
                if (p != null && anchorInBounds) {
                    var b:Rectangle = layoutBounds, x:Number, y:Number;
                    x = (p.x < b.left ? b.left : (p.x > b.right ? b.right : p.x));
                    y = (p.y < b.top ? b.top : (p.y > b.bottom ? b.bottom : p.y));
                    p = new Point(x, y);
                }
                super.layoutAnchor = p;
            }

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

            /**
             * Creates a new Distortion.
             * @param distortX Flag indicating if x-coordinates should be distorted
             * @param distortY Flag indicating if y-coordinates should be distorted
             * @param distortSize Flag indicating is sizes should be distorted
             */
            public function Distortion(distortX:Boolean=true, distortY:Boolean=true,
                distortSize:Boolean=true, distortAxes:Boolean=true)
            {
                this.distortX = distortX;
                this.distortY = distortY;
                this.distortSize = distortSize;
                this.distortAxes = distortAxes;
            }

            /** @inheritDoc */
            public override function operate(t:Transitioner=null):void
            {
                _t = (t!=null ? t : Transitioner.DEFAULT);
                _b = layoutBounds;
                visualization.data.visit(distort, Data.NODES);
                _t = null;

                if (distortAxes && visualization.xyAxes)
                    visualization.addEventListener(
                        VisualizationEvent.UPDATE, axesDistort);
            }

            /**
             * Distortion method for processing a DataSprite.
             * @param d a DataSprite to distort
             */
            protected function distort(d:DataSprite):void
            {
                var o:Object = _t.$(d), ss:Number;
                if (resetSize) {
                    if (useSizeField) { o.size = 1; }
                    else { o.scaleX = 1; o.scaleY = 1; }
                }
                var bb:Rectangle = _t.endBounds(d, d.parent);

                if (distortX) o.x = xDistort(o.x);
                if (distortY) o.y = yDistort(o.y);
                if (distortSize) {
                    ss = sizeDistort(bb, o.x, o.y);
                    if (useSizeField) {
                        o.size *= ss;
                    } else {
                        o.scaleX *= ss;
                        o.scaleY *= ss;
                    }
                }
            }

            /**
             * Compute a distorted x-coordinate.
             * @param x the initial x-coordinate
             * @return the distorted s-coordinate
             */
            protected function xDistort(x:Number):Number
            {
                // for sub-classes
                return x;
            }

            /**
             * Compute a distorted y-coordinate.
             * @param y the initial y-coordinate
             * @return the distorted y-coordinate
             */
            protected function yDistort(y:Number):Number
            {
                // for sub-classes
                return y;
            }

            /**
             * Compute a distorted size value.
             * @param bb an object's bounding box
             * @param x the initial x-coordinate
             * @param y the initial y-coordinate
             * @return the distorted size value
             */
            protected function sizeDistort(bb:Rectangle, x:Number, y:Number):Number
            {
                // for sub-classes
                return 1;
            }

            /**
             * Performs distortion of Cartesian axes. As axis layout is recomputed
             * <em>after</em> the operators have run, we must distort the axes in
             * a separate step. This is accomplished by adding an update listener
             * on the visualization that invokes the axis distortion after the
             * axis layout has completed. This method is registered as the
             * listener callback.
             * @param evt the visualization update event
             */
            protected function axesDistort(evt:VisualizationEvent):void
            {
                _t = evt.transitioner;
                _t = (_t==null ? Transitioner.DEFAULT : _t);

                var axes:CartesianAxes = visualization.xyAxes;
                if (axes != null) {
                    distortAxis(axes.xAxis, true, false);
                    distortAxis(axes.yAxis, false, true);
                }
                visualization.removeEventListener(
                    VisualizationEvent.UPDATE, axesDistort);

                _t = null;
            }

            private function distortAxis(axis:Axis, xb:Boolean, yb:Boolean):void
            {
                var i:int, o:Object;

                // distort the axis labels
                var labels:Sprite = axis.labels;
                for (i=0; i<labels.numChildren; ++i) {
                    o = _t.$(labels.getChildAt(i));
                    if (xb && distortX) {
                        o.x = xDistort(o.x);
                    }
                    if (yb && distortY) {
                        o.y = yDistort(o.y);
                    }
                }

                // distort the axis gridlines
                var glines:Sprite = axis.gridLines;
                for (i=0; i<glines.numChildren; ++i) {
                    o = _t.$(glines.getChildAt(i));
                    if (xb && distortX) {
                        o.x1 = xDistort(o.x1);
                        o.x2 = xDistort(o.x2);
                    }
                    if (yb && distortY) {
                        o.y1 = yDistort(o.y1);
                        o.y2 = yDistort(o.y2);
                    }
                }
            }

        } // end of class Distortion
    }