1

I have a map of United States in .ai format. I would like to use it in my flex application.
The problem is the map is sliced on state lines, I would like to manipulate the States within the application.

Explanation: In my app I want to be able to click on a State to highlight it, and blast all other States out of the scene and then later bring back all the states back to make the entire country. Just like a jigsaw puzzle.

I imported the FXG file from Adobe Illustrator to my Flex application in Flash Builder 4 but I can't manipulate each state at a time!

would really appreciate help! cheers

Marci-man
  • 2,113
  • 3
  • 28
  • 76
  • 2
    In the Flex Application you'll need individual assets for each map piece if you want to manipulate them differently. Or else you're going to have to do some bitmap processing which just sounds like a very complex route. – JeffryHouser Apr 11 '13 at 00:30
  • thank you for the suggestion! I have already started going down that road... :( – Marci-man Apr 11 '13 at 00:40

2 Answers2

1

There is an FxgParser library but it doesn't seem to output state names if they are marked as a d:userLabel attribute in the .fxg file. You can modify the source to fix that though.

Here's how::

Edit fxgparser.parser.Path.as then add this namespace at the top of the class:

public static const aab:Namespace = new Namespace("aab", "http://ns.adobe.com/fxg/2008/dt");

then at the end of the parse() method add this:

target.name = data.currentXml.@aab::userLabel;

as Illustrator CS6 seems to add a userLabel attribute rather than id or something else and FxgParser by default ignores this.

Code will be pretty similar to the above:

package  {

    import flash.display.*;
    import flash.events.*;
    import flash.geom.Point;
    import flash.net.*;
    import flash.utils.Dictionary;

    import com.greensock.*;
    import fxgparser.FxgDisplay;

    [SWF(width='1368', height='936', backgroundColor='#ffffff', frameRate='30')]
    public class FXGTest extends Sprite{

        private var states:Sprite;//container holding each state Shape
        private var positions:Dictionary = new Dictionary();//original position to restore states to

        public function FXGTest() {
            addEventListener(Event.ADDED_TO_STAGE,init);
        }
        private function init(e:Event):void{
            new URLLoader(new URLRequest("usa-wikipedia.fxg")).addEventListener(Event.COMPLETE,fxgLoaded);
        }
        private function fxgLoaded(e:Event):void{
            //trace(e.target.data);
            var map:FxgDisplay = addChild(new FxgDisplay( new XML(e.target.data) )) as FxgDisplay;
            trace(listChildren(map));
            //map.item.item.item.*
            states = Sprite(Sprite(Sprite(map.getChildAt(0)).getChildAt(0)).getChildAt(0));
            for(var i:int = 0 ; i < states.numChildren; i++){
                var s:DisplayObject = DisplayObject(states.getChildAt(i));//can't add mouse listeners because the states are Shape instances
                positions[s.name] = new Point(s.x,s.y);//store the positions in a hash to get the state position by name
            }
            stage.addEventListener(MouseEvent.CLICK,handleClick);
        }
        private function handleClick(e:MouseEvent):void{
            if(e.target is Stage) restore();
            else blast(getObjectsUnderPoint(new Point(mouseX,mouseY))[0].name);//named shapes are stored in a sprite so use getObjetsUnderPoint
        }
        private function blast(name:String):void{
            var blastRadius:Number = stage.stageWidth+stage.stageHeight;//or some other number
            var c:DisplayObject = states.getChildByName(name);//selected
            for(var i:int = 0 ; i < states.numChildren; i++){
                var s:DisplayObject = states.getChildAt(i);
                if(s != c){//everything else goes except this
                    var angle:Number = Math.atan2(s.y-c.y,s.x-c.x);//direction relative to the clicked state
                    var x:Number = s.x + (Math.cos(angle) * blastRadius);//get a position offset from the clicked state (blast centre) 
                    var y:Number = s.y + (Math.sin(angle) * blastRadius);//outwards at a random angle
                    TweenLite.to(s,1+Math.random() * 2,{x:x,y:y});//tween with different speeds for a bit of depth
                }
            }
        }
        private function restore():void{
            for(var i:int = 0 ; i < states.numChildren; i++){
                var s:DisplayObject = DisplayObject(states.getChildAt(i));
                TweenLite.to(s,.5+Math.random() * .5,{x:positions[s.name].x,y:positions[s.name].y});//tween states back into the original positions
            }
        }
        private function listChildren(d:DisplayObjectContainer):String{
            var result = 'parent: '+d.name+'\n\t';
            for(var i:int = 0 ; i < d.numChildren; i++){
                var c:DisplayObject = d.getChildAt(i);
                result += '\tchild: '+c.name+'\n';
                if(c is DisplayObjectContainer) result += listChildren(DisplayObjectContainer(c));
            }
            return result;
        }


    }

}

You can also download the above class, modified fxgparser library and states fxg here and see a running demo here Alternatively you can use SVG instead of FXG.The same developer also has an SvgParser library which you can get like so:

svn export http://www.libspark.org/svn/as3/SvgParser

You can easily save your .ai file as an .svg file. Here's a simple example using the US map from wikipedia:

package  {
    import flash.display.*;
    import flash.events.*;
    import flash.geom.Point;
    import flash.net.*;
    import flash.utils.Dictionary;

    import svgparser.SvgDisplay;
    import com.greensock.*;

    [SWF(width='1368', height='936', backgroundColor='#ffffff', frameRate='30')]
    public class SVGTest extends Sprite {

        private var states:Sprite;//container holding each state Shape
        private var positions:Dictionary = new Dictionary();//original position to restore states to

        public function SVGTest() {
            addEventListener(Event.ADDED_TO_STAGE,init);//make sure we have the stage ready
        }
        private function init(e:Event):void{
            removeEventListener(Event.ADDED_TO_STAGE,init);//stage ready, load the svg
            new URLLoader(new URLRequest("http://upload.wikimedia.org/wikipedia/commons/3/32/Blank_US_Map.svg")).addEventListener(Event.COMPLETE,svgLoaded);
        }
        private function svgLoaded(e:Event):void{//svg loaded
            var map:SvgDisplay = addChild(new SvgDisplay( new XML(e.target.data) )) as SvgDisplay;//use the parser
            trace(listChildren(map));//quick debug of the maps display list
            states = Sprite(map.getChildAt(0));//we want to access the main container
            for(var i:int = 0 ; i < states.numChildren; i++){
                var s:DisplayObject = DisplayObject(states.getChildAt(i));//can't add mouse listeners because the states are Shape instances
                positions[s.name] = new Point(s.x,s.y);//store the positions in a hash to get the state position by name
            }
            stage.addEventListener(MouseEvent.CLICK,handleClick);
        }
        private function handleClick(e:MouseEvent):void{
            if(e.target is Stage) restore();
            else blast(getObjectsUnderPoint(new Point(mouseX,mouseY))[0].name);//named shapes are stored in a sprite so use getObjetsUnderPoint
        }
        private function blast(name:String):void{
            var blastRadius:Number = stage.stageWidth+stage.stageHeight;//or some other number
            for(var i:int = 0 ; i < states.numChildren; i++){
                var s:DisplayObject = states.getChildAt(i);
                if(s.name != name){//everything else goes except this
                    var angle:Number = Math.random() * Math.PI * 2;//pick a random angle
                    var x:Number = s.x + Math.cos(angle) * blastRadius;//get a position offset from the clicked state (blast centre) 
                    var y:Number = s.y + Math.sin(angle) * blastRadius;//outwards at a random angle
                    TweenLite.to(s,1+Math.random() * 2,{x:x,y:y});//tween with different speeds for a bit of depth
                }
            }
        }
        private function restore():void{
            for(var i:int = 0 ; i < states.numChildren; i++){
                var s:DisplayObject = DisplayObject(states.getChildAt(i));
                TweenLite.to(s,.5+Math.random() * .5,{x:positions[s.name].x,y:positions[s.name].y});//tween states back into the original positions
            }
        }
        private function listChildren(d:DisplayObjectContainer):String{//recursively traverse a display list and list children names
            var result = 'parent: '+d.name+'\n\t';
            for(var i:int = 0 ; i < d.numChildren; i++){
                var c:DisplayObject = d.getChildAt(i);
                result += '\tchild: '+c.name+'\n';
                if(c is DisplayObjectContainer) result += listChildren(DisplayObjectContainer(c));
            }
            return result;
        }

    }

}
George Profenza
  • 50,687
  • 19
  • 144
  • 218
1

I ended up converting fxg to MXML...

I used the following code to do it:

FXG to MXML Converter

I changed the

d:userlabel

into attribute ID and it worked out extremely well!...

Marci-man
  • 2,113
  • 3
  • 28
  • 76