在使用
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文件中并不会包含节点的图的信息,图显示出来会变得非常的乱,所有的节点都会堆在一起。
posted on 2006-05-26 21:13
llh 阅读(338)
评论(0) 编辑 收藏 所属分类:
GEF