JAVAC.JP
HOSHI TETSUYA 星鉄矢
2005/07/25
hossy@javac.jp
http://www.javac.jp
Google
WWW を検索 WWW.JAVAC.JP を検索

5-4 エッジに自己最適化を導入

ノード間に斥力を与え、エッジで結ばれたノード間に引力を与えることで、Graphの自己最適化を図ります。
Graphの自己最適化に関しては、おそらく様々な方法が存在し、斥力・引力を与えるこの方法は、最も単純な理論の一つではないでしょうか。
example
揺れを減らすよう、いろいろ工夫していますが、まだ、ちゃんと最適解にたどり着くルールを導入できていません。
前述のドラッグ機能を付加したままなので、ドラッグして配置替えをできます。
自己最適化計算のためにOptimiserクラスを導入しています。
Optimiser.as
---
class Optimiser{
    private var attract_const:Number=2;
    private var repulse_const:Number=0.5;
    private var natural_length:Number=100;
    private var dataStorage:GraphDataStorage;

    function Optimiser(){
    }

    public function setDataStorage(dataStorage:GraphDataStorage):Void{
        this.dataStorage=dataStorage;
    }

    public function attract(index:Number):Void{
        var index1:Number=dataStorage.getLineIndex1(index);
        var index2:Number=dataStorage.getLineIndex2(index);
        effect(index1,index2,-attract_const);
    }

    public function repulse(index1:Number,index2:Number):Void{
        effect(index1,index2,repulse_const);
    }

    private function effect(index1:Number,index2:Number,const:Number):Void{
        var x1:Number=dataStorage.getPointPosX(index1);
        var y1:Number=dataStorage.getPointPosY(index1);
        var x2:Number=dataStorage.getPointPosX(index2);
        var y2:Number=dataStorage.getPointPosY(index2);
        var dx:Number=x1-x2;
        var dy:Number=y1-y2;
        var radius=Math.sqrt(dx*dx+dy*dy)-natural_length;
        if(radius>=0 && radius<100){
            radius=100;
        }
        if(radius<=0 && radius>-100){
            radius=-100;
        }
        dx/=radius;
        dy/=radius;
        var new_x1:Number=x1+const*dx;
        var new_y1:Number=y1+const*dy;
        var new_x2:Number=x2-const*dx;
        var new_y2:Number=y2-const*dy;
        dataStorage.setPointPosition(index1,new_x1,new_y1);
        dataStorage.setPointPosition(index2,new_x2,new_y2);
    }
}
定数attract_const,repulse_const,natural_lengthのバランスをもう少し調整する必要があるかもしれません。
また、onEnterFrameを行うムービークリップをGraphcontrolに作成し、スレッドを回したり、Graphの再描画を行ったりしています。
GraphControl.as
---
import mx.events.EventDispatcher;

class GraphControl{
    private var path:MovieClip;
    private var stageMc:MovieClip;
    private var dataStorage:GraphDataStorage;
    private var drawing:DrawingControl;
    private var optimiser:Optimiser;
    private var ex:ExXML;
    private var STAGE_MC_DEPTH:Number=100;
    private var runnableMc:MovieClip;
    private var RUNNABLE_DEPTH:Number=999;
    private var ovalList:Array;

    function GraphControl(path:MovieClip){
        this.path=path;
        path.createEmptyMovieClip("stage_mc",STAGE_MC_DEPTH);
        stageMc=path.stage_mc;
        stageMc._x=320;
        stageMc._y=240;
        ovalList=new Array();
        dataStorage=new GraphDataStorage();
        drawing=new DrawingControl(stageMc);
        optimiser=new Optimiser();
        optimiser.setDataStorage(dataStorage);
        loadGraphData("GraphData.xml");
    }

    private function loadGraphData(address:String):Void{
        ex=new ExXML();
        EventDispatcher.initialize(ex);
        ex.onLoad=function(){
            var lo:Object=new Object();
            lo.target=this;
            lo.type="onLoadXML";
            this.dispatchEvent(lo);
        };
        ex.addEventListener("onLoadXML",this);
        ex.load(address);
    }

    public function onLoadXML():Void{
        var arr:Array=ex.firstChild.childNodes;
        var pointNodes:Array;
        var lineNodes:Array;
        for(var i:Number=0;i<arr.length;i++){
            var node:XMLNode=XMLNode(arr[i]);
            if(node.nodeName=="pointlist"){
                pointNodes=node.childNodes;
            }else if(node.nodeName=="linelist"){
                lineNodes=node.childNodes;
            }else{
                trace("XMLNode is invalid form.");
            }
        }
        for(var i:Number=0;i<pointNodes.length;i++){
            var node:XMLNode=XMLNode(pointNodes[i]);
            if(node.nodeName=="point"){
                var o:Object=node.attributes;
                var id:String=String(o.id);
                var x:Number=Number(o.x);
                var y:Number=Number(o.y);
                var index:Number=dataStorage.addPoint(id,x,y);
            }else{
                trace("XMLNode is invalid form.");
            }
        }
        for(var i:Number=0;i<lineNodes.length;i++){
            var node:XMLNode=XMLNode(lineNodes[i]);
            if(node.nodeName=="line"){
                var o:Object=node.attributes;
                var id:String=String(o.id);
                var point1:String=String(o.point1);
                var point2:String=String(o.point2);
                var index:Number=dataStorage.addLine(id,point1,point2);
            }else{
                trace("XMLNode is invalid form.");
            }
        }
        dataStorage.addPointIndexToLineLists();
        drawPoints();
        drawLines();
        setRunnable();
    }

    private function drawPoints():Void{
        var len:Number=dataStorage.getPointListLength();
        for(var i:Number=0;i<len;i++){
            var x:Number=dataStorage.getPointPosX(i);
            var y:Number=dataStorage.getPointPosY(i);
            var size:Number=20;
            var mc:MovieClip=drawing.drawOval(x,y,size);
            mc.setIndex(i);
            mc.setGraphControl(this);
            ovalList.push(mc);
        }
    }

    private function drawLines():Void{
        var len:Number=dataStorage.getLineListLength();
        for(var i:Number=0;i<len;i++){
            var index1:Number=dataStorage.getLineIndex1(i);
            var index2:Number=dataStorage.getLineIndex2(i);
            var x1:Number=dataStorage.getPointPosX(index1);
            var y1:Number=dataStorage.getPointPosY(index1);
            var x2:Number=dataStorage.getPointPosX(index2);
            var y2:Number=dataStorage.getPointPosY(index2);
            var size:Number=0;
            var color16:Number=0x000000;
            drawing.drawLine(x1,y1,x2,y2,size,color16);
        }
    }

    public function refreshLines():Void{
        drawing.removeLines();
        drawLines();
    }

    public function refreshOvals():Void{
        for(var i:Number=0;i<ovalList.length;i++){
            var mc:MovieClip=MovieClip(ovalList[i]);
            var x:Number=dataStorage.getPointPosX(i);
            var y:Number=dataStorage.getPointPosY(i);
            mc.setPosition(x,y);
        }
    }

    public function refreshGraph():Void{
        refreshOvals();
        refreshLines();
    }

    public function setPosition(index:Number,x:Number,y:Number):Void{
        dataStorage.setPointPosition(index,x,y);
    }

    private function setRunnable():Void{
        path.createEmptyMovieClip("runnable_mc",RUNNABLE_DEPTH);
        runnableMc=path.runnable_mc;
        EventDispatcher.initialize(runnableMc);
        runnableMc.onEnterFrame=function(){
            var lo:Object=new Object();
            lo.target=this;
            lo.type="onRunnable";
            this.dispatchEvent(lo);
        };
        runnableMc.addEventListener("onRunnable",this);
    }

    private function onRunnable():Void{
        var len:Number=dataStorage.getLineListLength();
        for(var i:Number=0;i<len;i++){
            optimiser.attract(i);
        }
        var len:Number=dataStorage.getPointListLength();
        for(var i:Number=0;i<len;i++){
            for(var j:Number=i+1;j<len;j++){
                optimiser.repulse(i,j);
            }
        }
        refreshGraph();
    }
}

OvalClass.as
---
import mx.events.EventDispatcher;

class OvalClass extends MovieClip{
    private var index;
    private var SCREEN_WIDTH:Number=640;
    private var SCREEN_HEIGHT:Number=480;
    private var CENTER_X:Number=320;
    private var CENTER_Y:Number=240;
    private var graphControl:GraphControl;
    private var mouseListener:MovieClip;
    private var MOUSE_LISTENER_DEPTH:Number=999;

    function OvalClass(){
    }

    public function setIndex(index:Number):Void{
        this.index=index;
    }

    public function setGraphControl(graphControl:GraphControl):Void{
        this.graphControl=graphControl;
    }

    private function onPress():Void{
        this.startDrag(false,-CENTER_X+_width/2,-CENTER_Y+_height/2
        ,SCREEN_WIDTH-CENTER_X-_width/2,SCREEN_HEIGHT-CENTER_Y-_height/2);
        addMouseListener();
    }

    private function onRelease():Void{
        this.stopDrag();
        removeMouseListener();
    }

    private function onReleaseOutside():Void{
        onRelease();
    }

    private function addMouseListener():Void{
        this.createEmptyMovieClip("mouseListener",MOUSE_LISTENER_DEPTH);
        EventDispatcher.initialize(mouseListener);
        mouseListener.onMouseMove=function(){
            var lo:Object=new Object();
            lo.target=this;
            lo.type="onMouseMoveHandler";
            this.dispatchEvent(lo);
        }
        mouseListener.addEventListener("onMouseMoveHandler",this);
    }

    private function removeMouseListener():Void{
        mouseListener.removeEventListener("onMouseMoveHandler",this);
        mouseListener.removeMovieClip();
    }

    private function onMouseMoveHandler():Void{
        var x:Number=_x;
        var y:Number=_y;
        graphControl.setPosition(index,x,y);
        graphControl.refreshLines();
    }

    public function setPosition(x:Number,y:Number):Void{
        _x=x;
        _y=y;
    }
}



BACKTOPNEXT




All Contents Copyright (C) 2005 HOSHI Tetsuya
Home