    package flare.vis.operator.filter
    {
        import flare.animate.Transitioner;
        import flare.vis.data.DataSprite;
        import flare.vis.data.EdgeSprite;
        import flare.vis.data.NodeSprite;
        import flare.vis.operator.Operator;

        import flash.utils.Dictionary;

        /**
         * Filter operator that sets visible all items within a specified graph
         * distance from a set of focus nodes.
         */
        public class GraphDistanceFilter extends Operator
        {
            /** An array of focal NodeSprites. */
            public var focusNodes:/*NodeSprite*/Array;
            /** Graph distance within which which items wll be visible. */
            public var distance:int;
            /** Flag indicating which graph links to traverse. */
            public var links:int;

            /**
             * Creates a new GraphDistanceFilter.
             * @param focusNodes an array of focal NodeSprites. Graph distance is
             *  measured as the minimum number of edge-hops to one of these nodes.
             * @param distance graph distance within which items will be visible
             * @param links flag indicating which graph links to traverse. The
             *  default value is <code>NodeSprite.GRAPH_LINKS</code>.
             */
            public function GraphDistanceFilter(focusNodes:Array=null,
                distance:int=1, links:int=3/*NodeSprite.GRAPH_LINKS*/)
            {
                this.focusNodes = focusNodes;
                this.distance = distance;
                this.links = links;
            }

            /** @inheritDoc */
            public override function operate(t:Transitioner=null):void
            {
                t = (t==null ? Transitioner.DEFAULT : t);

                // initialize breadth-first traversal
                var q:Array = [], depths:Dictionary = new Dictionary();
                for each (var fn:NodeSprite in focusNodes) {
                    depths[fn] = 0;
                    fn.visitEdges(function(e:EdgeSprite):void {
                        depths[e] = 1;
                        q.push(e);
                    }, links);
                }

                // perform breadth-first traversal
                var xe:EdgeSprite, xn:NodeSprite, d:int;
                while (q.length > 0) {
                    xe = q.shift(); d = depths[xe];
                    // -- fix to bug 1924891 by goosebumps4all
                    if (depths[xe.source] == undefined) {
                        xn = xe.source;
                    } else if (depths[xe.target] == undefined) {
                        xn = xe.target;
                    } else {
                        continue;
                    }
                    // -- end fix
                    depths[xn] = d;

                    if (d == distance) {
                        xn.visitEdges(function(e:EdgeSprite):void {
                            if (depths[e.target]==d && depths[e.source]==d) {
                                depths[e] = d+1;
                            }
                        }, links);
                    } else {
                        xn.visitEdges(function(e:EdgeSprite):void {
                            if (depths[e] == undefined) {
                                depths[e] = d+1;
                                q.push(e);
                            }
                        }, links);
                    }
                }

                // now set visibility based on traversal results
                visualization.data.visit(function(ds:DataSprite):void {
                    var visible:Boolean = (depths[ds] != undefined);
                    var alpha:Number = visible ? 1 : 0;
                    var obj:Object = t.$(ds);

                    obj.alpha = alpha;
                    if (ds is NodeSprite) {
                        var ns:NodeSprite = ds as NodeSprite;
                        ns.expanded = (visible && depths[ds] < distance);
                    }
                    if (t.immediate) {
                        ds.visible = visible;
                    } else {
                        obj.visible = visible;
                    }
                });
            }

        } // end of class GraphDistanceFilter
    }