天天看點

在GEF中如何使用DirectedGraph來對圖中的對象進行位置的排放

在使用GEF進行開發的時候,對于需要繪制的圖形的節點,往往除了模型對象本身之外,還需要有一個相應的“圖”對象來儲存圖中這個節點的位置,以及大小等圖相關,但是與業務模型無關的一個對象。而在一開始希望顯示一個初始模型檔案的時候,再對應儲存圖資訊的檔案不存在的情況下,如何能夠很好的顯示這個圖,是一個比較麻煩的問題,涉及到對布局算法的一些分析與實作。這片文章就是介紹,如何使用GEF内的DirectedGraph這個類以及其相應的布局算法類DirectedGraphLayout,來解決這個問題。

基本思想是:為GEF的EditPart模型生成一個DirectedGraph,然後使用DirectedGraphLayout來計算布局,最後将布局的結果通過GEF顯示出來。

在參考了GEF的flow example之後,對其代碼作了部分重構,寫了這片文章,希望對遇到同樣問題的同志能夠有一定的幫助。

首先引入一個接口:

public interface GraphBuilder {

       public void contributeNodesToGraph(DirectedGraph graph, Map map);

       public void contributeEdgesToGraph(DirectedGraph graph, Map map);

       public void applyGraphResults(DirectedGraph graph, Map map);

}

這個接口中定義了幾個方法,其含義從其方法名中可以猜出:

contributeNodesToGraph:将目前對象作為節點(Node)添加到DirectedGraph中。

contributeEdgesToGraph:将目前對象所對應的連線作為邊(Edge)添加到DirectedGraph中。

applyGraphResults:将圖中生成的布局資訊取出,對本對象進行重新布局。

接口中的graph參數就是儲存的圖的資訊,map參數維持一個對象到節點/邊的映射,使得每個對象能夠友善的找到其對應的圖中的節點或者邊。這個接口的使用,在後面會有涉及。下面先看看顯示圖的容器是如何建構的。

圖的容器定義為GraphDiagramEditPart,這個EditPart對應于要顯示的有向圖的容器。它實作了GraphBuider接口,這也是我們主要需要關注的接口:

public class GraphDiagramEditPart extends AbstractGraphicalEditPart implements

              GraphBuilder.

contributeNodesToGraph方法将自身作為節點添加到圖中,但是因為GraphDiagramEditPart對應的是容器,是以它不需要向圖中添加資訊,隻是調用其子EditPart,将其添加到圖中。

       public void contributeNodesToGraph(DirectedGraph graph, Map map) {

              for (int i = 0; i < getChildren().size(); i++) {

                     NodeEditPart activity = (NodeEditPart)getChildren().get(i);

                     activity.contributeNodesToGraph(graph, map);

              }

       }

contributeEdgesToGraph方法将這個EditPart的所有子EditPart取出,調用其contributeEdgesToGraph方法,通過這個方法,就可以将所有的邊添加到圖中了:

       public void contributeEdgesToGraph(DirectedGraph graph, Map map) {

              for (int i = 0; i < getChildren().size(); i++) {

                     NodeEditPart child = (NodeEditPart)children.get(i);

                     child.contributeEdgesToGraph(graph, map);

              }

       }

applyGraphResults方法将所有取出所有的子EditPart,并調用其applyGraphResults,使得圖中所生成的布局資訊能夠被應用到顯示上。

       public void applyGraphResults(DirectedGraph graph, Map map) {

              applyChildrenResults(graph, map);

       }

       protected void applyChildrenResults(DirectedGraph graph, Map map) {

              for (int i = 0; i < getChildren().size(); i++) {

                     GraphBuilder part = (GraphBuilder) getChildren().get(i);

                     part.applyGraphResults(graph, map);

              }

       }

下面要介紹的是NodeEditPart,它作圖中所有節點所對應的EditPart的抽象父類,也實作了GraphBuilder接口。每一個要做為節點添加到圖中的EditPart,應該繼承這個類。

public abstract class NodeEditPart extends AbstractGraphicalEditPart implements

              GraphBuilder{

       public void contributeNodesToGraph(DirectedGraph graph,

                     Map map) {

              Node n = new Node(this);

              n.outgoingOffset = 7;

              n.incomingOffset = 7;

              n.width = getFigure().getPreferredSize().width;

              n.height = getFigure().getPreferredSize().height;

              n.setPadding(new Insets(10,8,10,12));

              map.put(this, n);

              graph.nodes.add(n);

       }

       public void contributeEdgesToGraph(DirectedGraph graph, Map map) {

              List outgoing = getSourceConnections();

              for (int i = 0; i < outgoing.size(); i++) {

                     EdgeEditPart part = (EdgeEditPart)getSourceConnections().get(i);

                     part.contributeEdgesToGraph(graph, map);

              }

       }

       public void applyGraphResults(DirectedGraph graph, Map map) {

              Node n = (Node)map.get(this);

              getFigure().setBounds(new Rectangle(n.x, n.y, n.width, n.height));

              for (int i = 0; i < getSourceConnections().size(); i++) {

                     EdgeEditPart trans = (EdgeEditPart) getSourceConnections().get(i);

                     trans.applyGraphResults(graph, map);

              }

       }

}

再就是邊所對應EditPart的抽象類EdgeEditPart。每一個要作為邊添加到圖中的EditPart,需要繼承這個類。在上面NodeEditPart中對其所對應的Figure其實并沒有什麼要求,但是對EdgeEditPart所對應的Figure,要求其Figure必須由一個BendpointConnectionRouter,作為其ConnectionRouter:setConnectionRouter(new BendpointConnectionRouter())。這樣圖的邊的路徑資訊才能夠被顯示出來。

public abstract class EdgeEditPart extends AbstractConnectionEditPart implements

              GraphBuilder {

       public void contributeEdgesToGraph(DirectedGraph graph, Map map) {

              Node source = (Node)map.get(getSource());

              Node target = (Node)map.get(getTarget());

              Edge e = new Edge(this, source, target);

              e.weight = 2;

              graph.edges.add(e);

              map.put(this, e);

       }

       public void applyGraphResults(DirectedGraph graph, Map map) {

              Edge e = (Edge)map.get(this);

              NodeList nodes = e.vNodes;

              PolylineConnection conn = (PolylineConnection)getConnectionFigure();

              conn.setTargetDecoration(new PolygonDecoration());

              if (nodes != null) {

                     List bends = new ArrayList();

                     for (int i = 0; i < nodes.size(); i++) {

                            Node vn = nodes.getNode(i);

                            int x = vn.x;

                            int y = vn.y;

                            if (e.isFeedback) {

                                   bends.add(new AbsoluteBendpoint(x, y + vn.height));

                                   bends.add(new AbsoluteBendpoint(x, y));

                            } else {

                                   bends.add(new AbsoluteBendpoint(x, y));

                                   bends.add(new AbsoluteBendpoint(x, y + vn.height));

                            }

                     }

                     conn.setRoutingConstraint(bends);

              } else {

                     conn.setRoutingConstraint(Collections.EMPTY_LIST);

              }

       }

}

最後的就是一個LayoutManager來初始化圖的建立,以及對圖的資訊進行解釋了,生成最終布局了。這個GraphLayoutManager作為GraphDiagramEditPart所對應的GraphDiagram的LayoutManager,來顯示圖的内容。

public class GraphLayoutManager extends AbstractLayout {

       private GraphBuilder diagram;

       GraphLayoutManager(GraphBuilder diagram) {

              this.diagram = diagram;

       }

       protected Dimension calculatePreferredSize(IFigure container, int wHint,

                     int hHint) {

              container.validate();

              List children = container.getChildren();

              Rectangle result = new Rectangle().setLocation(container

                            .getClientArea().getLocation());

              for (int i = 0; i < children.size(); i++)

                     result.union(((IFigure) children.get(i)).getBounds());

              result.resize(container.getInsets().getWidth(), container.getInsets()

                            .getHeight());

              return result.getSize();

       }

       public void layout(IFigure container) {

              DirectedGraph graph = new DirectedGraph();

              Map partsToNodes = new HashMap();

              diagram.contributeNodesToGraph(graph, partsToNodes);

              diagram.contributeEdgesToGraph(graph, partsToNodes);

              new DirectedGraphLayout().visit(graph);

              diagram.applyGraphResults(graph, partsToNodes);

       }

}

可以看到在layout方法中,首先生成了一個DirectedGraph,并調用了contributeNodesToGraph以及contributeEdgesToGraph方法,将節點和邊的資訊添加到這個生成的DirectedGraphGraph中,然後使用布局算法DirectedGraphLayout().visit(graph)來計算生成圖的資訊(這裡使用了visitor模式)最後調用applyGraphResults将圖的資訊應用到圖形的顯示上。

至此,所有架構的工作做完了,如果要将模型作為一個有向圖顯示的話,隻需要将模型的容器對象對應于GraphDiagramEditPart(在EditPartFactory中進行映射),為每一個需要表示為節點的對象,對應到一個繼承于NodeEditPart的EditPart,為每一個需要表示為邊的模型對象,對應到一個繼承于EdgeEditPart的EditPart。這樣,就能夠将圖的布局算法,應用到GEF架構中了。

這裡寫的比較簡單,使用起來也會有一些具體的限制。例如在有向圖中,是不能夠有孤立的節點的。如果使用CompoundDirectedGraph,就不會有這樣的問題,CompoundDirectedGraph可以包括子圖,可以支援更為複雜的圖形。在Flow Example中使用的就是CompoundDirectedGraph。在後面,我或許會将這個架構進行改寫,以使其支援CompoundDirectedGraph來進行布局算法。下面的圖是一個生成的例子,大家可以看一下效果:

這是從OWL檔案中讀取内容之後生成的一個圖的表示。可以看到,OWL的節點通過自動圖的自動布局之後,已經有了較好的視覺效果。如果沒有這樣的布局的話,因為單純的OWL檔案中并不會包含節點的圖的資訊,圖顯示出來會變得非常的亂,所有的節點都會堆在一起。

繼續閱讀