在GEF中如何使用DirectedGraph來對圖中的對象進(jìn)行位置的排放
在使用GEF進(jìn)行開發(fā)的時候,對于需要繪制的圖形的節(jié)點,往往除了模型對象本身之外,還需要有一個相應(yīng)的“圖”對象來保存圖中這個節(jié)點的位置,以及大小等圖相關(guān),但是與業(yè)務(wù)模型無關(guān)的一個對象。而在一開始希望顯示一個初始模型文件的時候,再對應(yīng)保存圖信息的文件不存在的情況下,如何能夠很好的顯示這個圖,是一個比較麻煩的問題,涉及到對布局算法的一些分析與實現(xiàn)。這片文章就是介紹,如何使用GEF內(nèi)的DirectedGraph這個類以及其相應(yīng)的布局算法類DirectedGraphLayout,來解決這個問題。
基本思想是:為GEF的EditPart模型生成一個DirectedGraph,然后使用DirectedGraphLayout來計算布局,最后將布局的結(jié)果通過GEF顯示出來。
在參考了GEF的flow example之后,對其代碼作了部分重構(gòu),寫了這片文章,希望對遇到同樣問題的同志能夠有一定的幫助。
首先引入一個接口:
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:將當(dāng)前對象作為節(jié)點(Node)添加到DirectedGraph中。
contributeEdgesToGraph:將當(dāng)前對象所對應(yīng)的連線作為邊(Edge)添加到DirectedGraph中。
applyGraphResults:將圖中生成的布局信息取出,對本對象進(jìn)行重新布局。
接口中的graph參數(shù)就是保存的圖的信息,map參數(shù)維持一個對象到節(jié)點/邊的映射,使得每個對象能夠方便的找到其對應(yīng)的圖中的節(jié)點或者邊。這個接口的使用,在后面會有涉及。下面先看看顯示圖的容器是如何構(gòu)建的。
圖的容器定義為GraphDiagramEditPart,這個EditPart對應(yīng)于要顯示的有向圖的容器。它實現(xiàn)了GraphBuider接口,這也是我們主要需要關(guān)注的接口:
public class GraphDiagramEditPart extends AbstractGraphicalEditPart implements
GraphBuilder.
contributeNodesToGraph方法將自身作為節(jié)點添加到圖中,但是因為GraphDiagramEditPart對應(yīng)的是容器,因此它不需要向圖中添加信息,只是調(diào)用其子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取出,調(diào)用其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,并調(diào)用其applyGraphResults,使得圖中所生成的布局信息能夠被應(yīng)用到顯示上。
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,它作圖中所有節(jié)點所對應(yīng)的EditPart的抽象父類,也實現(xiàn)了GraphBuilder接口。每一個要做為節(jié)點添加到圖中的EditPart,應(yīng)該繼承這個類。
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);
}
}
}
再就是邊所對應(yīng)EditPart的抽象類EdgeEditPart。每一個要作為邊添加到圖中的EditPart,需要繼承這個類。在上面NodeEditPart中對其所對應(yīng)的Figure其實并沒有什么要求,但是對EdgeEditPart所對應(yīng)的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來初始化圖的創(chuàng)建,以及對圖的信息進(jìn)行解釋了,生成最終布局了。這個GraphLayoutManager作為GraphDiagramEditPart所對應(yīng)的GraphDiagram的LayoutManager,來顯示圖的內(nèi)容。
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,并調(diào)用了contributeNodesToGraph以及contributeEdgesToGraph方法,將節(jié)點和邊的信息添加到這個生成的DirectedGraphGraph中,然后使用布局算法DirectedGraphLayout().visit(graph)來計算生成圖的信息(這里使用了visitor模式)最后調(diào)用applyGraphResults將圖的信息應(yīng)用到圖形的顯示上。
至此,所有框架的工作做完了,如果要將模型作為一個有向圖顯示的話,只需要將模型的容器對象對應(yīng)于GraphDiagramEditPart(在EditPartFactory中進(jìn)行映射),為每一個需要表示為節(jié)點的對象,對應(yīng)到一個繼承于NodeEditPart的EditPart,為每一個需要表示為邊的模型對象,對應(yīng)到一個繼承于EdgeEditPart的EditPart。這樣,就能夠?qū)D的布局算法,應(yīng)用到GEF框架中了。
這里寫的比較簡單,使用起來也會有一些具體的約束。例如在有向圖中,是不能夠有孤立的節(jié)點的。如果使用CompoundDirectedGraph,就不會有這樣的問題,CompoundDirectedGraph可以包括子圖,可以支持更為復(fù)雜的圖形。在Flow Example中使用的就是CompoundDirectedGraph。在后面,我或許會將這個框架進(jìn)行改寫,以使其支持CompoundDirectedGraph來進(jìn)行布局算法。下面的圖是一個生成的例子,大家可以看一下效果:
這是從OWL文件中讀取內(nèi)容之后生成的一個圖的表示。可以看到,OWL的節(jié)點通過自動圖的自動布局之后,已經(jīng)有了較好的視覺效果。如果沒有這樣的布局的話,因為單純的OWL文件中并不會包含節(jié)點的圖的信息,圖顯示出來會變得非常的亂,所有的節(jié)點都會堆在一起。
posted on 2005-07-22 17:42 Living Not Striving 閱讀(2381) 評論(4) 編輯 收藏 所屬分類: GEF