JGraph指南[下]

定制Graph显示

       JGraph本身提供了一些不错的绘图效果,能够定制的功能不多。可以通过setGridColorsetGridSize修改网格,可以通过setHandleColorSetLockedHandleColor改变选定的颜色,背景颜色可以通过setBackground进行设置。

       如果你想控制图元的渲染,可以继承缺省的渲染器,重载paint函数。渲染器继承自Component,基于Cell的属性进行渲染。JGraph及其继承类并不直接进行渲染操作,二是利用Cell的渲染代码。一个新的渲染器通过重载CellViewgetRendererComponent方法,或者AbstractCellViewgetRenderer来关联到Cell

       下面的代码展示了如何创建新的Cell TypeRenderer。代码向Graph中添加一个椭圆形顶点。最简单的方法是继承JGraph类,因为JGraph实现了CellViewFactory接口,她负责创建视图。

       当创建一个视图,如果Cell不是边和端口的实例,JGraph假设Cell是一个顶点,并且调用createVertexView方法。因此我们只需要重写该方法来实现椭圆形顶点,返回相应的视图。

// Overrides JGraph.createVertexView
protected VertexView createVertexView(Object v,GraphModel model,CellMapper cm) {
       // Return an EllipseView for EllipseCells
       if (v instanceof EllipseCell)
              return new EllipseView(v, model, cm);
       // Else Call Superclass
       return super.createVertexView(v, model, cm);
}


       椭圆形顶点通过EllipseCell类来表示,它继承自DefaultGraphCell,并不提供其他附件的方法。它只是用来区别椭圆形顶点和一般的顶点。

// Define EllipseCell
public class EllipseCell extends DefaultGraphCell {
       // Empty Constructor
       public EllipseCell() {
              this(null);
       }
       // Construct Cell for Userobject
       public EllipseCell(Object userObject) {
              super(userObject);
       }
}


       EllipseView需要定义椭圆的特殊显示。它包含一个内部类,来提供渲染所需要的代码。视图和渲染器都分别继承自VertexViewVertexRender。需要重载getPerimeteroint来返回椭圆的边界点,重载getRenderer返回正确的渲染器,重载paint进行Cell的绘制。

// Define the View for an EllipseCell
public class EllipseView extends VertexView {
       static EllipseRenderer renderer = new EllipseRenderer();
       // Constructor for Superclass
       public EllipseView(Object cell, GraphModel model,CellMapper cm) {
              super(cell, model, cm);
}
// Returns Perimeter Point for Ellipses
public Point getPerimeterPoint(Point source, Point p) { ...
}
// Returns the Renderer for this View
protected CellViewRenderer getRenderer() {
              return renderer;
}
// Define the Renderer for an EllipseView
static class EllipseRenderer extends VertexRenderer {
              public void paint(Graphics g) { ... }
       }
}


       重载getRenderer,而不是getRendererComponent的原因是我们继承的AbstractCellView已经提供了一个缺省的CellViewRender

       提示框(Tooltips)可以通过重载JGraphgetToolTipText来实现,getToolTipText继承自JComponent。下面的代码当鼠标指向Cell时,用提示框来用显示CellLabel

// Return Cell Label as a Tooltip
public String getToolTipText(MouseEvent e) {
       if(e != null) {
       // Fetch Cell under Mousepointer
       Object c = getFirstCellForLocation(e.getX(), e.getY());
       if (c != null)
              // Convert Cell to String and Return
              return convertValueToString(c);
       }
       return null;
}


Graph必须通过SwingTooltipManager进行注册才能使用提示框。可以在启动时调用如下代码实现:

ToolTipManager.sharedInstance().registerComponent(graph)

       如果一个Graph显示了非常复杂的图形结构,通过一个属性对话框来进行编辑,比直接原地编辑方便许多。为了实现对话框编辑,BasicGraphUItstartEditingcompleteEditing必须被重载。为了在Graph中使用该UIGraphupdateUI方法也必须重载。

// Define a Graph with a Custom UI
public class DialogGraph extends JGraph {
       // Sets the Custom UI for this graph object
       public void updateUI(){
              // Install a new UI立即发表               setUI(new DialogUI());
              invalidate();
       }
}

DialogUI获得了视图的编辑权力,然后放入到一个对话框中,在该对话框是一个模式对话框(会锁定父对话框)。DialogUI的详细代码到网上去找吧。

动态修改Graph

GraphView环境中包含的是CellViewGraphView允许编辑CellViewGraphModel允许插入、删除、编辑GraphCell

JGraph区分ModelViewModel通过GraphModel定义,它包含实现GraphCell接口的类。View通过GraphView定义,它包含实现CellView接口的类。CellView之间的映射被CellMapper接口定义:

JGraph指南[下]

一个模型拥有0个或多个View,对每一个Model中的每个Cell,每个GraphView只含有一个CellView。这些对象的状态通过一个Map的键值对来表示。每个CellView将它自己的属性和对应的GraphCell的属性进行合并。属性合并时,GraphCell的属性有相对于CellView高的优先级。

Cell的状态通过它的属性表示。不论什么情况下,GraphConstant类通过两步来改变Cell的状态:

l  创建变化对象

l  ModelView上执行变化

JGraph指南[下]

       为了创建变化对象,调用GraphConstants类的createMap函数获得一个新的Map对象。当一个Map应用到ViewCell的状态上,它并替换已经存在的Map。新Map的项会被添加或者修改。如果需要,可以调用GraphConstantssetRemoveAttibutessetRemoveAll方法来删除已有状态Map中的一个或者全部状态。

       CellView的状态发生改变,或者它的邻居修改了属性,CellViewupdate将用来更新Cell的状态。

       可以将Model看做两种结构的访问点:图元结构和群组结构。图元结构是数学定义的,比如顶点和边。群组结构用来组合Cell,比如父亲和孩子。

       图元结构通过getSourcegetTarget定义,它们分别返回对象的源和目的端口。返回的端口是顶点的孩子,它允许点之间多个连接的存在。

       群组结构通过getChildgetChildCountgetIndexOfChildgetParent定义。如果一个对象没有父亲,它就是根节点(root),可以通过getRootAtgetRootCount进行访问。

       下面的方法创建一个新的DefaultGraphCell,并将它添加到Model中。同时添加两个端口到Cell上,创建一个Cell的属性Map,属性键值对可以通过GraphConstants进行类型安全的访问。

void insertVertex(Object obj, Rectangle bounds) {
       Map attributeMap = new Hashtable();
       // Create Vertex
       DefaultGraphCell cell = new DefaultGraphCell(userObject);
       // Create Attribute Map for Cell
       Map map = GraphConstants.createMap();
       GraphConstants.setBounds(map, bounds);
       // Associate Attribute Map with Cell
       attributeMap.put(cell, map);
       // Create Default Floating Port
       DefaultPort port = new DefaultPort("Floating");
       cell.add(port);
       // Additional Port Bottom Right
       int u = GraphConstants.PERCENT;
       port = new DefaultPort("Bottomright");
       // Create Attribute Map for Port
       map = GraphConstants.createMap();
       GraphConstants.setOffset(map, new Point(u, u));
       // Associate Attribute Map with Port
       attributeMap.put(port, map);
       cell.add(port)
       // Add Cell (and Children) to the Model
       Object[] insert = new Object[]{cell};
       model.insert(insert, null, null, attributeMap);
}

insertVertex的第一个参数是用户对象,它包含了关联到Cell的数据信息。用户对象可以是String,也可以是用户自定义的对象。如果是自定义对象,应该实现对象的toString方法,以便可以获得Cell显示的字符串。第二个参数辨识顶点的边界,它被存储为一个属性。

attributeMap参数并不被Model使用,它传递给View,提供创建Cell View的属性信息。insert函数的第三个参数是存储在Model的属性。

由于端口被看做是是Model中的普通孩子,GraphModel接口可以用来找到顶点的缺省端口。

Port getDefaultPort(Object vertex, GraphModel model) {
       // Iterate over all Children
       for (int i = 0; i < model.getChildCount(vertex); i++) {
              // Fetch the Child of Vertex at Index i
              Object child = model.getChild(vertex, i);
              // Check if Child is a Portif (child instanceof Port)
              // Return the Child as a Port
              return (Port) child;
       }
       // No Ports Found
       return null;
}

上面的代码使用第一个端口作为缺省端口。核心API不提供这样的功能的原因在于,缺省端口往往是应用所特有的。

下面的函数创建一个新的DefaultEdge,并将其添加到Model中,同时包含特定源和目的端口。

void insertEdge(Object obj, Port source, Port target) {
       // Create Edge
       DefaultEdge edge = new DefaultEdge(userObject);
       // Create ConnectionSet for Insertion
       ConnectionSet cs = new ConnectionSet(edge, source, target);
       // Add Edge and Connections to the Model
       Object[] insert = new Object[]{edge};
       model.insert(insert, cs, null, null);
}

第一个参数Cell的用户对象,第二个和第三个参数是新边的源和目的端口。为了在Model中插入连接,一个ConnectionSet实例是必需的。该实例用来收集新边的源端口和目的端口,并在一个单独的事物中执行变化。

如果一个CellModel中删除,Model检查Cell是否拥有孩子。如果有,更新群组结构,所有的父亲和孩子不会被删除。如果一个Cell同它的孩子一起删除,它可以重新插入,而不需要孩子和ParentMap。如果只删除Cell不删除其孩子,操作的结果打散了群组结构。

JGraph指南[下]

如上图所示,群组A包含一个Cell B和一个群组C,群组C包含一个Cell E和一个群组D,群组D包含Cell FCell G。删除CD的结果如右图所示。

下面的代码删除所有选择的Cell,包含他的所有后代:

// Get Selected Cells
Object[] cells = graph.getSelectionCells();
       if (cells != null) {
       // Remove Cells (incl. Descendants) from the Model
       graph.getModel().remove(graph.getDescendants(cells));
}

ConnectionSetParentMap用来修改Model。属性可以通过包含Cell和属性键值对的Map修改。

Edgeportvertex分别是DefaultEdgeDefaultPortDefaultGraphCell的实例,它们都在Model之中。下面的代码修改edge的源到portport的父亲修改为vertex,并且vertex的用户对象是字符串“Hello World”。

// Create Connection Set
ConnectionSet connectionSet = new ConnectionSet();
connectionSet.connect(edge, port, true);
// Create Parent Map
ParentMap parentMap = new ParentMap();
parentMap.addEntry(port, vertex);
// Create Properties for VertexMap
properties = GraphConstants.createMap();
GraphConstants.setValue(properties, "Hello World");
// Create Property Map
Map propertyMap = new Hashtable();
propertyMap.put(a, properties);
// Change the Model
model.edit(connectionSet, propertyMap, parentMap, null);

edit的最后一个参数类型是UndoableEdit[]。指定的edit是事务的一部分,它会并行地修改ModelView

每个GraphCell拥有一个或多个CellView,它包含Cell的属性。CellView的属性可能被父类GraphVieweditCell修改。与前面使用的propertyMap不同,在这使用attributeMap参数来向Model中插入CellattributeMap参数包含一个CellView实例来作为键,属性同样表示为键值对。下面的代码修改vertex的边界颜色为黑色:

// Work on the Graph’s View
GraphView v = graph.getView();
// Create Attributes for Vertex
Map attributes = GraphConstants.createMap();
GraphConstants.setBorderColor(attributes, Color.black);
// Get the CellView to use as Key
CellView cellView = v.getMapping(vertex, false);
// Create Attribute Map
Map attributeMap = new Hashtable();
attributeMap.put(cellView, properties);
// Change the View
v.editCells(attributeMap);


 

你可能感兴趣的:(JGraph指南[下])