JUNGJUNGJUNGJUNG:Java Java Java Java 平台网络////图应用开发的一种通用基础架构 编者按:众多需要处理多个对象间复杂关系的应用软件,例如网络管理、人际关系网络、思维脑图、地理导航 以 及多 种游 戏等 ,在 本质 上都 是对 “图”的 计算 ; JUNG 就 是专 为图 ( Graph) 的计 算和 可视 化提 供的 一个 通用 的 可扩 充的 Java 编 程平 台。 本文 深入 浅出 地以 多个 实例 对 JUNG 编 程做 了较 为详 细的 讲解 。您 可以 在本 刊网 站 下 载本 文全 部的 样例 源码 。 摘 要: 以 循序 渐进 的实 例说 明在 JUNG 架 构下 进行 网络 /图 编程 的方 法。 关 键词 : JUNG,Java, 图, 网络 ,树 ,数 据结 构 1.1.1.1. 概述 JUNG(Java Universal Network/Graph framework)是 一个 Java 开 源项 目 ,其 目的 在于 为开 发关 于图 或网 络结 构的 应用程序提供一个易用、通用的基础架构。使用JUNG 功能调用,可以方便的构造图或网络的数据结构,应用 经 典算 法( 如聚 类、 最短 路径 , 最大 流量 等), 编写 和测 试用 户自 己的 算法 ,以 及可 视化 的显 示数 据的 网络 图 。 本 文使 用尽 可能 简明 的代 码 示范基于JUNG 应 用开 发方 法, 希望 对有 开发 复杂 网络 /图 应用 需求 的编 程人 员有 所 帮 助。 2.2.2.2. 开 发环 境配 置 本 文使 用 MyEclipse7.5 作 为开 发环 境, 文中 的例 子皆 在 JUNG2.0.1 下 调试 通过 。 JUNG 包 可以 在 SourceForge 下 载, 其地 址为 : http://sourceforge.net/projects/jung/files/ JUNG 包 中包 含所 有需 要的 jar 文 件, 将其 解压 到一 个目 录中 即可 。 在MyEclipse 中 新建 一个 Java 工程,并 使用 Configure Build Path 菜 单命 令激 活 “Libraries”选 项卡 ,然 后使 用 “Add Extenal JARs”按钮将JUNG包中的Jar文 件全 部设 为系 统类 库 ,这 样在 该工 程中 建立 的Java程 序就 可以 访 问JUNG 所 提供 的接 口方 法及 数据 了; 本文 中的 程序 均在 该工 程内 编写 。 3.3.3.3. 创 建图 的数 据结 构 JUNG 提 供的 最基 本的 图结 构的 接口 是 Graph,其中V和E分 别为 顶点 和边 的数 据类 型 。该 接口 提供 创建 图 结构 的基 本方 法, 包括 : 添 加或 者删 除顶 点和 边; 获 取所 有顶 点和 边到 集合 类; 获 取边 和顶 点的 属性 (如 边的 权重 等 ); JUNG 支 持一 系列 不同 类型 的图 ,以 子接 口的 形式 出现 ,例 如: 有向 图 DirectedGraph, 森林 Forest, 树Tree和 无向 图 UndirectedGraph等。 作为第一个简单例子,我们以实现Graph 等接口的SparseGraph 为例,来构造一个图。SparseGraph 代表一个允 许 有向 或无 向边 的稀 疏图 (如 果你 希望 支持 并行 边, 可以 使用 SparseMultigraph 类), 使用 addVertex 和addEdge 方 法可 以方 便地 添加 顶点 和边 ;下 面的 CreateGraph.java 代 码构 造一 个包 含三 个顶 点和 两个 边的 一个 图 (顶 点和 边 的数 据类 型我 们都 定义 为字 符串 ;当 然你 也可 以使 用任 何其 它的 数据 类型 ): SparseMultigraph g = new SparseMultigraph(); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addEdge("Edge-A", "1", "2"); g.addEdge("Edge-B", "2", "3"); System.out.println("The graph g = " + g.toString()); 该 段代 码运 行的 结果 为: The graph g = Vertices:3,2,1 Edges:Edge-B[2,3] Edge-A[1,2] 注意toString 方 法非 常完 整地 显示 出该 图的 所有 信息 。 如 果要 构造 有向 图, 只需 要在 addEdge 方 法中 包含 edgeType 参 数即 可, 例如 : g.addEdge("Edge-C", "3", "2", EdgeType.DIRECTED); 添加一条从顶点 “3”到顶点 “2”的有向边。 4.4.4.4. 图 的可 视化 表现 图 的可 视化 表现 是 JUNG 最 为吸 引人 的地 方。 JUNG 可 视化 功能 的基 本组 件是 BasicVisualizationServer 类 (隶 属 edu.uci.ics.jung.visualization 包),该类是Swing JPanel 的一个子类,因此可以放置到任何一个Swing 的容器对象 中。 要构造一个BasicVisualizationServer 类,首先需要了解JUNG 中“布局(Layout)”的概念。一个Layout 接口的 实现,实 际上 就是 要告 诉 JUNG 如 何来 绘制 图的 顶点 和边 ,即 确定 其在 在图 形界 面上 的坐 标位 置 (x,y)。使 用预 定 义的Layout 实现(例如edu.uci.ics.jung.algorithms.layout.CircleLayout)可 以方 便的 完成 这个 工作 而不 需要 繁杂 的 手 工编 码。 因此 图 g的 可视 化表 现可 以分 以下 4个 步骤 完成 : //1. 初 始化 图 g //2. 使 用该 图创 建布 局对 象 Layout layout = new CircleLayout(g); //3. 使 用布 局对 象创 建 BasicVisualizationServer 对象 BasicVisualizationServer vv = new BasicVisualizationServer( layout); //4. 将 上述 对象 放置 在一 个 Swing 容 器中 并显 示之 frame.getContentPane().add(vv); frame.pack(); frame.setVisible(true); 显 示效 果如 下图 所示 ,完 整代 码参 见 GraphView.java。 图1 最简单的网络图显示 5.5.5.5. 网 络图 的修 饰 图1的网络图中,没有关于顶点和边的描述信息。如果希望添加文字描述信息,或者修改边的线型和颜色等可 视 化属 性, 就需 要理 解变 换器 ( Transformer) 和渲 染语 境( RenderContext) 两个 概念 。 变 换器 (Transformer)是 来源 于 Apache Commons Collections 开 源项 目的 一个 接口 ,它 负责 将进 入集 合中 的对 象 变 换成 为另 一种 对象 类型 (例 如提 取关 于这 些对 象的 特定 的信 息 )。例如,如 果要 确定 顶点 的绘 图方 式 ,可 使用 接口Transformer,该 接口 负责 将给 定的 顶点 v转 换成 为一 个 Point2D 类 型的 对象 ,其 中包 含了 v的 (x, y)坐 标等 信息 。 RenderContext 负责JUNG 渲染器(Render)的各项参数,并最终影响可视化的表现。RenderContext (定义在 edu.uci.ics.jung.visualization)可 以从 BasicVisualizationServer 对 象得 到 ,其 影响 的显 示属 性包 括顶 点颜 色 ,边 的线 型 ,顶 点和 边的 文本 标签 等。 下面 是几 个常 用的 隶属 于 RenderContext 的 函数 : setVertexFillPaintTransformer() setEdgeStrokeTransformer() setVertexLabelTransformer() setEdgeLabelTransformer() 这 些函 数均 需要 使用 相应 的变 换器 作为 参数 ,以 将顶 点和 边转 换为 渲染 器所 需要 的信 息。 以 setVertexFillPaintTransformer()为 例, 它需 要一 个类 型为 Transformer的 参数 ,以 根据 顶点 确定 不同 的Paint 类型;注意:我 们例 子中 的该 变换 器的 第一 个参 数是 String 类型,这 是因 为它 是由 顶点 的数 据类 型所 决 定的,而 顶点 的数 据类 型我 们前 面已 经设 为 String 了;系 统可 以根 据这 个 String 参 数区 分不 同的 顶点 。假 设我 们 希 望让 John 节 点显 示为 绿色 ,其 它的 节点 显示 黄色 ,则 可以 这样 来构 造这 个变 换器 ,注 意观 察其 中的 if 代 码是 如 何使 用第 一个 参数 来区 别不 同的 顶点 的: Transformer vertexPaint = new Transformer() { public Paint transform(String s) { if (s.equals("John")) return Color.GREEN; else return Color.YELLOW; } }; setVertexLabelTransformer()和setEdgeLabelTransformer()用 于指 定边 和顶 点的 文本 标签 ,其 参数 的数 据类 型分 别 为 Transformer和Transformer,其中V和E分 别为 顶点 和边 的数 据类 型 。但是JUNG 提 供了 一 个 非常 简单 的 “工 具变 换器 ”ToStringLabeller(源于edu.uci.ics.jung.visualization.decorators),用 于利 用顶 点或 者 边的toString()方 法得 到一 个相 应的 文本 标签 变换 器; 因此 ,下 面的 代码 可以 方便 的设 置顶 点的 标签 而无 需自 行 编 码变 换器 : BasicVisualizationServer 对象.getRenderContext().setVertexLabelTransformer(new ToStringLabeller()); 完 整的 程序 GraphView2.java 的 运行 结果 如图 2所 示。 图2 带颜色标记和文本标签的网络图 6.6.6.6. 自 定义 顶点 和边 的数 据类 型 上 面的 例子 中顶 点和 边只 有最 简单 的意 义 ,也 就是 说 ,只 有一 个名 字 (一 个字 符串 )。在 实际 应用 中 ,节 点和 边 往 往具 有复 杂的 数据 意义 ,例如,在 人际 关系 网络 中 ,一 个节 点可 能代 表一 个人 或机 构 ,具 有名 称 、联 系方 式 、 隶 属机 构甚 至包 括照 片等 多媒 体信 息或 附件 。一 个复 合形 式的 顶点 或边 的数 据结 构可 以是 任何 Java 类 ,但 一般 说 来起 码应 当提 供 id 属 性和 toString()方 法, 前者 用于 区别 不同 的网 络元 素, 后者 一般 用于 为图 形元 素增 加文 本 标签。作 为一 个例 子 ,我 们构 造一 个顶 点为 联系 人 (包 含自 增的 id 和 名字 属性 )、边 是联 系方 式的 图 ;顶 点类 命 名为People, 其中 id 是 一个 自增 的属 性, 每次 构造 一个 新的 顶点 其 id 会增1; 在构 造函 数中 需要 指定 其姓 名。 class People { static int sid = 0; int id; String name; People(String name) { id = ++sid; this.name = name; } public String toString() { return name + "-" + id; } } 边 的类 命名 为 Link,除 了自 增的 id 属 性以 外 ,contact 属 性标 记其 联系 方式 。注意People 和Link 类 都有 toString 方 法以 返回 一个 合适 的文 本标 签。 class Link { static int sid = 0; int id; String contact; Link(String contact) { id = ++sid; this.contact = contact; } public String toString(){ return contact+"("+id+")"; } } 对 于这 种自 定义 的顶 点和 边, 图和 渲染 类以 及变 换器 的定 义都 需要 做相 应修 改, 以配 合其 数据 类型 。例 如我 们 可 以这 样声 明相 关变 量: SparseGraph g; BasicVisualizationServer vv; Transformer vertexPaint; Transformer edgeStrokeTransformer; 在 增加 4个 顶点 和四 条边 以后 ,样 例程 序 ComplexGraph 的 输出 如图 3所 示。 图3 自定义顶点和边的数据类型 7.7.7.7. 从 数据 文件 中读 入图 如 果图 的连 结信 息存 储在 数据 文件 中 ,可 以使 用相 应的 函数 将其 转换 为 Graph 类型。有 多种 存储 图数 据的 方式 , 包 括邻 接矩 阵与 邻接 表等 ;下 面以 邻 接矩 阵 为 例来 读入 图。 所谓邻 接矩 阵 是 这样 定义 的: 设一 个有 n(n>0)个 顶点 的图 , V(G)={v1, v2, …, vn}, 其邻 接矩 阵 AG是 一个 n阶 二 维矩 阵, 该矩 阵中 如果 vi 至vj 有 一条 边, 则 (i, j)项 的值 为 1, 否则 为 0。 如下 一个 邻接 矩阵 : 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 对 应的 网络 图如 图 4所 示。 图4 从邻接矩阵文件读入的图 下 面我 们就 来从 数据 文件 构造 这个 图 。为 了实 现这 个目 的 ,我 们首 先要 了解 “工厂”(Factory)的 概念 。Factory 是 定义 在开 源项 目 org.apache.commons.collections 包 下面 的一 个接 口 ,用 于自 定义 对象 的创 建过 程 (相 当于 一个 简 化的 Transformer), 其核 心是 create()函 数, 此函 数返 回该 工厂 创建 的对 象。 读 入邻 接矩 阵的 函数 是 MatrixFile, 其原 型为 : public MatrixFile(Map weightKey, Factory> graphFactory, Factory vertexFactory, Factory edgeFactory) 该 函数 有四 个参 数, 由于 其 API 文 档没 有及 时更 新, 不能提 供正 确的 调用 信息 ,我 们只 能通 过分 析其 源码 (主 要 代码 在 edu.uci.ics.jung.algorithms.matrix.GraphMatrixOperations.matrixToGraph()中)来 研究 其用 法 。首 先看 三 个 Factory 参 数: graphFactory: 用于 创建 邻接 矩阵 对应 的图 。假 设我 们需 要一 个 SparseGraph 对 象, 其顶 点和 边的 数据 类型 分别 为Integer 和String, 则可 以这 样来 初始 化这 个参 数: Factory> graphFactory; graphFactory = new Factory>() { public SparseGraph create() { return new SparseGraph(); } }; 上 面的 第一 行代 码 ,将graphFactory 类 型化 (parameterize,或 翻译 为 “参 数化 ”)为SparseGraph 类,而该SparseGraph 的 顶点 和边 分别 为 Integer 和String 类型。由于MatrixFile 需 要使 用 graphFactory 来 创建 我们 需要 的图 对象 ,因此 在create 函 数( 第三 行) 中, 只需 要简 单的 创建 一个 正确 类型 化的 SparseGraph 对 象即 可。 vertexFactory 用 于创 建顶 点对 象 ;由 于在 邻接 矩阵 中没 有关 有顶 点的 其他 信息 ,我 们只 需要 将顶 点按 顺序 排序 命 名 即可 ,因 此我 们的 顶点 采用 Integer 类 型, 并使 用递 增的 静态 变量 做计 数器 : Factory vertexFactory; Factory edgeFactory; static int vi = 0, ei = 0;// counter for creating vertics & edges vertexFactory = new Factory() { public Integer create() { return new Integer(vi++); } }; edgeFactory = new Factory() { public String create() { return "" + ei++; } }; 参 数初 始化 完毕 后, 读入 和创 建图 就非 常简 单了 : MatrixFile mf = new MatrixFile(null, graphFactory, vertexFactory, edgeFactory); Graph g = mf.load("c:/g.txt"); System.out.println(g); 注意MatrixFile 的 第一 个参 数暂 且用 不到 ,置为null;后 面处 理有 权图 时需 要设 置这 个参 数 ;完 整的 代码 请参 见 ReadMatrixFile.java, 控制 台输 出的 运行 结果 为: Vertices:0,1,2,3,4,5,6,7,8,9 Edges:13[9,4] 14[9,7] 11[8,3] 3[3,0] 2[2,4] 1[1,9] 10[7,5] 0[0,8] 7[5,8] 6[5,0] 5[4,6] 4[3,9] 9[6,9] 8[6,2] 8.8.8.8. 应 用关 于图 的算 法 JUNG 中 实现 了多 种关 于图 的算 法 ,例 如最 短路 径 ,k近 邻聚 类 ,最 小生 成树 等 ;当 然你 也可 以编 写自 己的 算法 。 下 面以 最短 路径 为例 ,演 示如 何使 用 JUNG 进 行图 的计 算和 显示 。 将JNUG 中 已经 实现 的内 置算 法 ,应 用于 一个 图是 很简 单的 ;以 上一 节构 造的 无权 图为 例 ,应用Dijkstra 算 法求 从 顶点 “0”到 顶点 “9”的 最短 路径 的代 码如 下: DijkstraShortestPath alg = new DijkstraShortestPath(g); List l = alg.getPath(new Integer(0), new Integer(9)); System.out.println("The shortest unweighted path from 0 to 9 is:"); System.out.println(l.toString()); 算 法类 DijkstraShortestPath 的 实例 化同 样是 需要 类型 化为 和图 一致 ,然 后使 用 getPath 函 数即 可获 取最 短路 径。 控 制台 文本 输出 的信 息为 : The shortest unweighted path from 0 to 9 is: [3, 4] 其 意为 通过 边 “3”和边“4”的 路径 是最 短的 。显 然, 如果 能够 使用 图形 化的 表现 方式 来展 现最 短路 径就 更好 了 。为 了达 到这 个目 的, 我们 定义 两个 “笔 画( stroke)”, 其中 shortestStroke 使 用粗 笔画 来显 示最 短路 径: final Stroke edgeStroke = new BasicStroke(1); final Stroke shortestStroke = new BasicStroke(4);//thick edge line! 然 后创 建一 个 笔 画变 换器 , 根据 上面 计算 得到 的最 短路 径来 判断 使用 那种 笔画 : Transformer edgeStrokeTransformer = new Transformer() { public Stroke transform(String s) { for(int i=0;i g, Transformer nev) 即 可, 变换 器的 构造 方法 如下 述代 码所 示( 假设 我们 的边 还是 字符 串类 型, eWeights 是 一个 Map, 存储 有边 及 其 对应 的权 值的 映射 ): // converts a string (of an edge) to the edge's weight Transformer nev = new Transformer() { public Double transform(String s) { Number v = (Number) eWeights.get(s); if (v != null) return v.doubleValue(); else return 0.0; } }; 除 了可 以获 得最 短路 径的 边之 外, 我们 还可 以获 得最 短路 径的 值: Number dist = alg.getDistance(new Integer(0), new Integer(9)); System.out.println("and the length of the path is: " + dist); 上 面的 讨论 是基 于我 们已 经构 造了 有权 图 g和 权值 映射 eWeights。但是,如 果希 望以 类似 MatrixFile 的 形式 从邻 接 矩阵 中直 接读 入非 1的 权值 ,则 需要 一些 更为 技巧 性的 工作 ,直 接使 用下 述代 码是 不行的: MatrixFile mf = new MatrixFile(eWeights, graphFactory, vertexFactory, edgeFactory); 因 为直 到目 前的 版本 (2.0.1),JUNG 在MatrixFile 读 入数 据时 ,并没有使 用到 它的 第一 个参 数 “Map weightKey”( 可以 认为 这是 一个 还没 有消 除的 Bug)。 如 何解 决这 个问 题呢 ?通 过研 究其 源码 ,我 们使 用如 下的 技术 来间 接使 用 JUNG 的 接口 : 首 先在 程序 开始 时初 始化 一个 Map 用 于存 储边 /权 映射 ;因为Map 是 一个 接口 ,为 方便 起见 ,我 们使 用 Hashtable 作 为其 实现 : Hashtable eWeights = new Hashtable(); 然 后使 用如 下代 码从 矩阵 数据 C:/g2.txt 中 读入 有权 图: Graph g; try { BufferedReader reader = new BufferedReader(new FileReader("c:/g2.txt")); DoubleMatrix2D matrix = createMatrixFromFile(reader);//!!! g = GraphMatrixOperations.matrixToGraph(matrix, graphFactory, vertexFactory, edgeFactory,eWeights);//!!! reader.close(); } catch (IOException ioe) { throw new RuntimeException("Error in loading file " + "c:/g2.txt", ioe); } 矩 阵数 据 g2.txt 的 内容 假设 为: 0 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 7 0 0 0 1 0 0 0 0 0 0 0 9 0 0 0 3 0 0 0 0 0 0 6 0 0 0 0 0 3 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 9 0 0 5 0 0 6 0 0 请 注意 上面 代码 中带 下划 线的 两个 部分 。函 数 createMatrixFromFile()是从MatrixFile 类 的源 码中 复制 过来 的 ,因 为 该函 数是 一个 private 类 型的 方法 ,不 能直 接调 用, 我们 只能 采用 “复 制粘 贴 ”的 形式 来使 用它 : private DoubleMatrix2D createMatrixFromFile(BufferedReader reader) throws IOException { List> rows = new ArrayList>(); String currentLine = null; while ((currentLine = reader.readLine()) != null) { StringTokenizer tokenizer = new StringTokenizer(currentLine); if (tokenizer.countTokens() == 0) { break; } List currentRow = new ArrayList(); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); currentRow.add(Double.parseDouble(token)); } rows.add(currentRow); } int size = rows.size(); DoubleMatrix2D matrix = new SparseDoubleMatrix2D(size, size); for (int i = 0; i < size; i++) { List currentRow = rows.get(i); if (currentRow.size() != size) { throw new IllegalArgumentException( "Matrix must have the same number of rows as columns"); } for (int j = 0; j < size; j++) { double currentVal = currentRow.get(j); if (currentVal != 0) { matrix.setQuick(i, j, currentVal); } } } return matrix; } 而matrixToGraph 则 可以 接收 一个 Map 型 的参 数, 以将 各个 边的 权值 存入 该映 射中 。 最 后, 我们 希望 在可 视化 界面 中显 示边 的权 值, 而不 是在 生成 边时 的顺 序号 ;要 做到 这一 点只 需要 增加 一 个 EdgeLabelTransformer 即 可: Transformer ev = new Transformer(){ public String transform(String s) { Number v = (Number) myApp.eWeights.get(s); if (v != null){ return "----"+v.intValue(); } else return ""; } }; vv.getRenderContext().setEdgeLabelTransformer(ev); 注 意这 个变 换器 和本 节开 始处 的权 值变 换器 非常 类似 ,因 为他 们的 功能 都是 通过 边的 到权 值, 只是 对变 换器 的 返 回结 果类 型有 不同 要求 。 预 期在 未来 的 JUING 版 本中 ,我 们可 以通 过简 单地 功能 调用 : MatrixFile mf = new MatrixFile(eWeights, graphFactory, vertexFactory, edgeFactory); Graph g = mf.load("C:/g2.txt"); 来 实现 上述 读入 有权 邻接 矩阵 的目 的。 完整 程序 参见 ReadMatrixFile2.java, 其显 示结 果为 : 图6 带权图的最短路径 为 了清 晰起 见, 图 6在权 值的 前面 加了 些 “----”以 将不 同的 边的 权值 区别 开来 。 10.10.10.10. 响 应鼠 标输 入对 图进 行变 换 JUNG 支 持的 图的 变换 ,包 括缩 放( Scalling)、 移动 ( Transforming)、 选择 ( Picking)、 编辑 ( Editing) 等。 如 果 要使 用这 些默 认的 鼠标 交互 操作 ,只 需要 将 BasicVisualizationServer 升 级为 支持 鼠标 交互 的 VisualizationViewer, 并且 添加 下面 代码 即可 : // this is how we activate the mouse! // Create a graph mouse and add it to the visualization component DefaultModalGraphMouse gm = new DefaultModalGraphMouse(); gm.setMode(ModalGraphMouse.Mode.PICKING); vv.setGraphMouse(gm); 这 里面 的核 心概 念是 所谓 的 “模 态图 鼠标 (ModalGraphMouse)”接口,定 义在 edu.uci.ics.jung.visualization.control 包 中。 该接 口定 义了 与图 交互 的鼠 标操 作应 该支 持的 行为 ,其 常用 的实 现包 括 DefaultModalGraphMouse, AnnotatingModalGraphMouse,EditingModalGraphMouse等 ;后 两者 用于 交互 式批 注和 编辑 。如 果只 需 要进 行普 通的 视图 变换 ,使 用 DefaultModalGraphMouse 即 可, 如上 例所 示。 使 用这 样的 代码 ,我 们可 以对 图 6那样CircleLayout 自 动生 成的 布局 ,用 鼠标 选择 并拖 动顶 点, 改变 图的 表现 , 使 之变 成图 7所 示的 模样 。你 可以 试试 滚动 鼠标 的滚 轮, 看看 有什 么别 的效 果, 或者 按下 Shift 键 或者 Ctrl 键, 看 看选 择顶 点的 时候 又有 什么 效果 ! 一 般说 来, Picking 状 态下 系统 内置 的交 互操 作包 括: 1)直 接左 键选 择并 拖动 :移 动顶 点。 2)Shift+左 键选 择: 选择 多个 顶点 。 3)Ctrl+左 键选 择: 使被 选择 的顶 点居 中。 4)滚 轮: 缩放 视图 。 而在Transforming 模 式下 内置 的交 互操 作包 括: 1)直 接左 键选 择并 拖动 :移 动视 图。 2)Shift+左 键拖 动: 旋转 视图 。 3)Ctrl+左 键拖 动: 切变 (扭 曲) 视图 。 4)滚 轮: 缩放 视图 。 图7 使用 Picking 模式选择 并拖动顶点改变图的布局 简 单交 互的 完整 程序 请参 见 Interact.java; 该程 序基 本上 是从 ReadMatrixFile2.java 修 改而 来的 。如 果还 希望 在运 行 时切 换选 择 (Picking)和 移动 (Transforming)模式,可 以将 DefaultModalGraphMouse 内 置的 键盘 监听 器添 加 到 系统 中即 可: vv.addKeyListener(gm.getModeKeyListener()); 该 代码 支持 在运 行时 点击 “p”键 切换 到选 择模 式, 点击 “t”键 切换 到移 动模 式。 但 是如 果出 于某 种目 的, 你不 希望 使用 DefaultModalGraphMouse 内 置的 全部 交互 功能 ,而 只希 望使 用其 中一 部 分 ,例 如平 移和 缩放 ,则 可以 使用 如下 的方 式构 造一 个 “可 插入 图鼠 标( PluggableGraphMouse)”, 并插 入需 要 的“鼠 标插 件 ”即 可: PluggableGraphMouse gm = new PluggableGraphMouse(); gm.add(new TranslatingGraphMousePlugin(MouseEvent.BUTTON1_MASK)); gm.add(new ScalingGraphMousePlugin(new CrossoverScalingControl(), 0, 1.1f, 0.9f)); vv.setGraphMouse(gm);//vv is the VisualizationViewer Object PluggableGraphMouse 可 以容 纳各 种鼠 标插 件 (mouse plugin)以 实现 特定 的交 互任 务 。所谓鼠 标插 件 ,就 是一 个 实 现了 定义 在 edu.uci.ics.jung.visualization.control 中的GraphMousePlugin 接 口的 类; JUNG 提 供了 多种 现成 的鼠 标 插件 ,例如TranslatingGraphMousePlugin,ScalingGraphMousePlugin 等等,我 们只 需要 构造 若干 合适 的鼠 标插 件 并将 其添 加到 PluggableGraphMouse 中 即可 ;如 上例 所示 ,则 应用 程序 只接 收平 移和 缩放 操作 ,而 忽略 其他 交 互 请求 。 11.11.11.11. 图 的可 视化 编辑 图 的编 辑实 际上 是鼠 标交 互的 一种 特殊 情况 ,也 就是 要使 用 EditingModalGraphMouse 鼠 标插 件。 因此 ,我 们 将 ReadMatrixFile.java 稍 作修 改, 保留 边和 顶点 的工 厂函 数, 去掉 从文 件中 读入 图的 部分 ,仅 构造 一个 空 的 SparseGraph, 并添 加下 述代 码: //!!!this is how we can edit the graph!!! EditingModalGraphMouse gm = new EditingModalGraphMouse(vv.getRenderContext(), vertexFactory, edgeFactory); vv.setGraphMouse(gm); 程序EditGraph.java 的 运行 截图 参见 图 8。 之 所以 EditingModalGraphMouse 要 使用 创建 顶点 和边 的工 厂类 ,是 因为 该鼠 标插 件的 功能 就是 要动 态地 添加 顶 点 和边 。( 附注 :原 来的 CircleLayout 布 局也 改成 了简 单的 静态 布局 StaticLayout,JUNG 文 档中 说明 这种 布局 中 顶 点的 位置 由一 个 Map 对 象初 始化 ,并 且也 可以 在以 后从 该映 射中 获取 ;但 是直 到本 文所 用版 本, 该静 态布 局 仅 仅只 是上 级类 AbstractLayout 的 一个 简单 继承 )。图8所 示截 图中 已经 添加 了 5个 顶点 (鼠 标左 键单 击即 可添 加 节点 ), 正在 顶点 4和3之 间添 加边 。 EditingModalGraphMouse 实 际上 是由 多个 鼠标 插件 组合 而成 的, 包括 PickingGraphMousePlugin、 TranslatingGraphMousePlugin、ScalingGraphMousePlugin、EditingGraphMousePlugin、AnnotatingGraphMousePlugin、 EditingPopupGraphMousePlugin 等,这 些鼠 标插 件的 不同 组合 形成 三种 不同 的模 式 :Transforming、Picking、Editing; 这 三种 模式 可以 通过 EditingModalGraphMouse.setMode()方 法来 设置 ,也 可以 通过 一个 内置 的菜 单( 称 为 ModeMenu) 来设 置。 将 ModeMenu 添 加到 窗体 菜单 条上 的代 码是 : //***************this is how we can toggle mouse mode // Let's add a menu for changing mouse modes JMenuBar menuBar = new JMenuBar(); JMenu modeMenu = gm.getModeMenu(); // Obtain mode menu from the mouse modeMenu.setText("Mouse Mode"); modeMenu.setIcon(null); // I'm using this in a main menu modeMenu.setPreferredSize(new Dimension(80,20)); // Change the size menuBar.add(modeMenu); frame.setJMenuBar(menuBar); 图8 正在顶点 4和3之间添加边的操作的截图 需 要说 明的 是, EditingModalGraphMouse 内 置支 持鼠 标右 键菜 单, 但是 直到 版本 2.0.1, 这个 菜单 仍有BugBugBugBug,多 次 点击 时会 生成 重复 的菜 单项 。而 且在 很多 时候 ,我 们需 要根 据自 定义 的数 据类 型来 决定 弹出 菜单 的项 目, 需 要 自定 义属 性编 辑菜 单。 下面 以一 个更 为复 杂和 全面 的例 子来 介绍 自定 义的 显示 和编 辑模 式。 12.12.12.12. 自 定义 顶点 形状 下 面以 一个 “脑 图( mind map)”应 用为 例, 来看 看图 的显 示和 编辑 中各 种自 定义 模式 的实 现。 所 谓脑 图, 即各 种概 念或 想法 之间 的关 系的 网络 图, 是一 种常 用的 思考 问题 和辅 助决 策的 工具 ;一 个简 单的 英 文 单词 脑图 可以 象图 9那 样: 围绕 单词 mind, 有多 个相 关的 单词 节点 ;显 然这 样的 脑图 对于 单词 的理 解和 记忆 会 有帮 助的 。下 面我 们先 来构 造这 样一 个脑 图的 数据 结构 。 图9 一个关于单词的脑图构造样例 假 设在 我们 的脑 图中 ,首先,顶 点类 命名 为 Idea,边 类命 名为 Link,其 定义 分别 在源 码 Idea.java 和Link.java 中。 在Idea 中,除 了最 基本 的 id(数 值标 识 ),title(文 本标 识 )等 字段 外 ,我 们还 引入 一个 Vector 类型links 字段(用 于 存储 该顶 点到 其他 相关 顶点 的链 接 ),以 及相 应的 添加 联接 的函 数 addLink()。除 此之 外 ,还 提供 一个 工厂 类用 于 自动 创建 一个 空的 Idea 对 象, 其中 的静 态方 法用 于方 便的 获得 该工 厂的 实例 。主 要代 码如 下: public class Idea { static long _id = 0;// seed for generating id long id;// unique id of this idea String title, brief;// essential data Vector links;// pointer to other nodes public Idea(String title, String brief, Vector keywords) { this.id = _id++; this.title = title; this.brief = brief; if (keywords == null) this.keywords = keywords; else this.keywords = new Vector(); this.links = new Vector(); } public void addLink(Link link){ this.links.add(link); } public String toString(){//good habit! return title; } } class IdeaFactory implements Factory { static IdeaFactory instance = new IdeaFactory(); public Idea create() { return new Idea("", "", new Vector()); } public static IdeaFactory getInstance() { return instance; } } 类 似的 , Link 的 定义 如下 ,其 中的 LinkFactory 与Idea 中 的类 似, 从略 : public class Link { static long _id = 0;// seed for generating id long id;// unique id Idea toIdea; String title, brief; public Link(Idea toIdea, String title, String brief) { this.id = _id++; this.toIdea = toIdea; this.title = title; this.brief = brief; } public String toString(){ return title; } } class LinkFactory implements Factory {//略 } 使 用上 述结 构的 顶点 和边 的定 义, 可以 按下 面方 式进 行: Idea id1 = new Idea("mind", "", null); Idea id2 = new Idea("idea", "", null); Link l12 = new Link(id2, "create", ""); id1.addLink(l12); g.addVertex(id1); g.addEdge(l12, id1, id2); 上 面代 码只 初始 化了 两个 顶点 和一 条边 ;现 在我 们采 用自 定义 的模 式来 显示 这样 结构 的图 。一 种简 单的 方法 是 使用JUNG 系 统内 置的 “顶 点形 状工 厂 ”( 定义 在 edu.uci.ics.jung.visualization.util 包 中) 获得 需要 的形 状, 例如 方 形、 多边 形等 。该 工厂 的构 造形 式为 : VertexShapeFactory(Transformer vst, Transformer vart) 也 就是 说 ,我 们需 要两 个变 换器 参数 来构 造的 该工 厂 ,其中vsf 负 责从 顶点 获取 顶点 显示 尺寸 (size),varf 负责 从 顶点 获取 高宽 比 (aspect ratio)。由 于我 们希 望在 顶点 中使 用 16点 的字 体显 示单 词 (英 文字 符大 约宽 8像素), 因 此我 们设 置顶 点形 状的 显示 尺寸 为: 字 符数 *8+16, 其中 16是 两端 的留 空值 。据 此公 式我 们可 以设 计出 顶点 到 显示 尺寸 的变 换器 : // 1. transformer from vertex to its size (width) Transformer vst = new Transformer() { public Integer transform(Idea i) { int len = i.toString().length(); if (len < 3) len = 3; return new Integer(len * 8 + 16); } }; 根 据顶 点的 宽度 len, 我们 容易 推导 出, 如果 希望 顶点 的高 度是 20像 素的 话, 则需 要的 高宽 比应 该是 2/len,因 此 ,我 们设 计出 高宽 比变 换器 为: // 2. transformer from vertex to its shape's "aspect ratio" Transformer vart = new Transformer() { public Float transform(Idea i) { int len = i.toString().length(); if (len < 3) len = 3; return new Float(2.0 / len); } }; 有 了上 面两 个参 数, 就可 以构 造出 需要 “顶 点形 状工 厂 ”了: //3. create the shape factory final VertexShapeFactory vsf = new VertexShapeFactory(vst, vart); 我 们真 正需 要的 ,是 一个 “顶 点形 状变 换器 ”,因此,使 用上 述工 厂 ,可 以构 造系 统支 持度 圆形 、方形、星 型等 的 变换 器; 以圆 角方 形为 例, 我们 的 vertex shape transformer 为: // 4. EASY way to have a "vertex shape transformer" Transformer vstr = new Transformer() { public Shape transform(Idea i) { return vsf.getRoundRectangle(i); } }; 最 后, 将该 形状 变换 器置 入渲 染语 境中 即可 : //5. put the shape transformer to render context, done! vv.getRenderContext().setVertexShapeTransformer(vstr); 程 序的 运行 结果 如图 10所 示。 图10 内置的圆角矩形顶点形状 由 于使 用 setVertexShapeTransformer 可 以接 受到 任意 形状 的变 换器 ,所 以可 以实 现任 意形 状的 顶点 显示 ;例 如 , 如 果你 对 Java2D 比 较熟 悉, 完全 可以 使用 自己 编码 来实 现 上述 效果 。因 此图 10的 第二 种实 现方 式为 : // Manually create a custom vertex shape vstr = new Transformer() { public Shape transform(Idea i) { int len = i.toString().length(); if (len < 4) len = 4; //Arc2D.Double r = new Arc2D.Double(); //r.setArc(-len * 5, -len * 3, len * 10, len * 6, 60, 240, Arc2D.PIE); RoundRectangle2D.Double r = new RoundRectangle2D.Double(-len * 5, -10, len * 10, 20 ,10 ,10); return r; } }; vv.getRenderContext().setVertexShapeTransformer(vstr); 你 可以 试试 使用 上面 代码 中带 下划 线的 两行 来替 代后 面的 关于 r的 定义 ,看 看有 何效 果 ——著 名的 吃豆 子的 小怪 物 出现 了! RenderContext 中 可以 设置 的 Transformer 有7项 之多 ,例 如: Transformer getVertexIconTransformer(); 用 于设 置顶 点图 标; 因此 ,我 们可 从图 像文 件构 造 ImageIcon 对 象, 并通 过一 个图 标变 换器 传递 给渲 染器 : final ImageIcon ii=new ImageIcon("shy.jpg"); Transformer vit=new Transformer(){ public Icon transform(Idea arg0) { return ii; } }; vv.getRenderContext().setVertexIconTransformer(vit); vv.getRenderer().getVertexLabelRenderer().setPosition(Position.E); 上 文中 最后 一行 的作 用是 将顶 点文 本显 示在 图标 的右 方 (东方)。图 片文 件 shy.jpg 放 在工 程文 件夹 中 ;效 果见 图 11。 这里 我们 使用 了统 一的 图标 ,您 当然 可以 为不 同的 顶点 提供 不同 的图 标。 本节 程序 的源 码在 Ex.java, 请注 意 其中 包含 了上 面所 说的 3种 方式 的代 码, 您需 要注 释掉 其中 的一 些语 句才 能分 别显 示其 中一 种。 图11 使用图标表示顶点 除 了使 用 Transformer 来 设置 渲染 语境 外 ,还 可以 直接 构造 自己 的渲 染器 ,例如DefaultVertexLabelRenderer 是一 个以JLabel 为 基类 的渲 染器 ,它 可以 使用 JLabel 的丰富 功能 来顶 点的 文本 标签 ,例 如多 行文 本, HTML 支 持, 图 标支 持等 等。 例如 下面 的渲 染器 可以 产生 如图 12的 效果 : //vertex label renderer with icon & html DefaultVertexLabelRenderer vlr = new DefaultVertexLabelRenderer(Color.BLUE){ public Component getVertexLabelRendererComponentgetVertexLabelRendererComponentgetVertexLabelRendererComponentgetVertexLabelRendererComponent(JComponent vv, Object value, Font font, boolean isSelected, Idea vertex) { super.getVertexLabelRendererComponent(vv, value, font, isSelected, vertex); setIcon(new ImageIcon("shy.jpg")); this.setBorder(BorderFactory.createEtchedBorder()); setText("title:
"+vertex.toString()+""); return this; } }; vv.getRenderContext().setVertexLabelRenderer(vlr); 图12 使用自定义的顶点标签渲染器 注 意我 们需 要继 承 DefaultVertexLabelRenderer 并覆盖getVertexLabelRendererComponent()方法; 该方 法也 是 VertexLabelRenderer 接 口中 定义 的唯 一一 个需 要实 现的 方法 。 图12看 上去 有些 乱 ,但 只要 注意 到每 个顶 点都 有两 个图 标 ,第 一个 是顶 点本 身的 图标 ,由 前文 所讲 的顶 点图 标 变 换器 VertexIconTransformer 生 成; 第二 个图 标是 顶点 标签 的一 部分 (你 可以 看到 我故 意加 上的 JLabel 边框), 由 自定 义的 渲染 器生 成。 需要 注意 的是 ,顶 点标 签的 图标 不能 替代 顶点 本身 ,因 为如 果顶 点如 果不 显示 的话 , 则 该图 无法 响应 鼠标 的 “选 择顶 点 ”的 操作 ;另外,如 果有 顶点 图标 的话 ,JUNG 就 不再 绘制 顶点 的形 状 (Shape), 也 就是 说, 如果 你同 时提 供了 顶点 图标 和顶 点形 状两 个变 换器 ,则 JUNG 优 先使 用图 标变 换器 。 13.13.13.13. 自 定义 弹出 式编 辑菜 单 JUNG 的 鼠标 支持 可以 通过 “鼠 标插 件 ”实 现。 实际 上, 我们 已经 用过 的支 持编 辑功 能的 “图 鼠标 ” EditingModalGraphMouse 就 是由 多个 MousePlugin 组 合而 成 ,并 根据 需要 添加 和移 除某 个 plugin;例如,“选择” (picking) 模式 的源 代码 为: protected void setPickingMode() { remove(translatingPlugin); remove(rotatingPlugin); remove(shearingPlugin); remove(editingPlugin); remove(annotatingPlugin); add(pickingPlugin); add(animatedPickingPlugin); add(labelEditingPlugin); add(popupEditingPlugin); } 从 中可 以看 到, 选择 “picking”模 式, 实际 上就 是移 除 translating( 平移 )等 5个plugin, 激活 picking 等4个 plugin。所 有的 这些 plugins 都 是继 承 AbstractPopupGraphMousePlugin 而 来的 ,而 后者 是 GraphMousePlugin 接口 的 一个 最简 单的 实现 。通 过将 各种 mouse plugins 安 装到 PluggableGraphMouse 对 象中 ,可 以构 造自 己的 鼠标 支 持 。因 此, 构造 一个 自定 义的 鼠标 插件 ,也 需要 按这 样的 思路 来进 行: 1) 继承AbstractPopupGraphMousePlugin; 2) 重 载其 中的 抽象 函数 handlePopup(MouseEvent e)方 法以 处理 弹出 菜单 。 好在Greg Bernstein(JUNG 项 目组 成员 之一 )已 经给 我们 实现 了一 个通 用的 处理 顶点 和边 的弹 出菜 单的 插件 : PopupVertexEdgeMenuMousePlugin.java 使 用该 插件 的最 简单 过程 是: 1) 先 构造 一个 内置 的图 鼠标 (例 如 EditingModalGraphMouse), 并移 除其 中的 弹出 菜单 支持 ; 2) 构 造一 个 PopupVertexEdgeMenuMousePlugin 对 象, 并设 置其 中的 顶点 和边 弹出 菜单 ; 3) 将 第上 一步 中的 构造 好的 插件 添加 到第 (1)步 构造 的图 鼠标 中。 样 例代 码如 下( 见 Ex1.java), 其中 的核 心语 句加 了下 划线 : // mouse plugin demo EditingModalGraphMouse gm = new EditingModalGraphMouse(vv.getRenderContext(), IdeaFactory.getInstance(), LinkFactory.getInstance()); PopupVertexEdgeMenuMousePlugin myPlugin = new PopupVertexEdgeMenuMousePlugin(); JPopupMenu edgeMenu = new JPopupMenu("Vertex Menu"); JPopupMenu vertexMenu = new JPopupMenu("Edge Menu"); edgeMenu.add(new JMenuItem("edge!")); vertexMenu.add(new JMenuItem("vertex!")); myPlugin.setEdgePopup(edgeMenu); myPlugin.setVertexPopup(vertexMenu); // Removes the existing popup editing plugin! gm.remove(gm.getPopupEditingPlugin()); // Add our new plugin to the mouse gm.add(myPlugin); vv.setGraphMouse(gm); 试 试分 别在 顶点 和边 上点 击鼠 标右 键, 会弹 出不 同的 菜单 。很 显然 ,上 面例 子中 的弹 出菜 单仅 仅区 分了 顶点 和 边 ,没 有体 现具 体的 被点 击对 象。 要想 构造 上下 文敏 感的 弹出 菜单 ,需 要先 研究 一 下 PopupVertexEdgeMenuMousePlugin.java 中 重载 的弹 出菜 单处 理方 法: protected void handlePopup(MouseEvent e) { final VisualizationViewer vv = (VisualizationViewer)e.getSource(); Point2D p = e.getPoint(); GraphElementAccessor pickSupport = vv.getPickSupport(); if(pickSupport != null) { final V v = pickSupport.getVertex(vv.getGraphLayout(), p.getX(), p.getY()); if(v != null) { // System.out.println("Vertex " + v + " was right clicked"); updateVertexMenu(v, vv, p); vertexPopup.show(vv, e.getX(), e.getY()); } else { final E edge = pickSupport.getEdge(vv.getGraphLayout(), p.getX(), p.getY()); if(edge != null) { // System.out.println("Edge " + edge + " was right clicked"); updateEdgeMenu(edge, vv, p); edgePopup.show(vv, e.getX(), e.getY()); } } } } 该 代码 的主 要职 责 ,是 在鼠 标点 击时 ,确 定事 件发 生的 位置 、被 点击 的对 象 (是 边还 是顶 点 ),并 根据 此信 息构 造 菜单 内容 并弹 出菜 单 (见 上文 中带 下划 线的 代码 );其中最 为重 要的 是 updateVertexMenu()或updateEdgeMenu() 方法,这 个方 法用 于在 菜单 弹出 前重 新构 造菜 单内 容 。正 如上 述代 码的 作者 所说 ,我 们不 需要 修改 handlePopup() 方 法, 而只 需要 修改 updateVertexMenu()或updateEdgeMenu()这 两个 函数 即可 。 以 顶点 菜单 的更 新为 例, 其函 数形 式为 : private void updateVertexMenu(V v, VisualizationViewer vv, Point2D point) 该 函数 有三 个参 数, 分别 是当 前被 选中 的顶 点 v, 当前 图的 显示 部件 vv, 以及 鼠标 点击 的位 置 point。 根据 这些 参 数, 我们 可以 编写 自己 的 updateVertexMenu()函 数, 以初 始化 其中 的 vertexPopup 和edgePopup 两 个变 量; 这 个 变量 都是 JPopupMenu 类 型, 其类 型声 明为 : private JPopupMenu edgePopup, vertexPopup; 作 为示 范, 我们 在顶 点菜 单中 设置 两个 菜单 项: 删除 当前 节点 和切 换 “是 否计 划项 目( isSchedule)”。 菜单 的构 造 和事 件响 应都 放在 VertexPopupMenu.java 中 ,其 核心 函数 为: public static JPopupMenu update(final Idea v, final VisualizationViewer vv, Point2D point) { //1. Clear the menu m.removeAll(); // 2. "delete" menu String title = v.title; JMenuItem mi = new JMenuItem("Delete [" + title + "]"); mi.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { vv.getGraphLayout().getGraph().removeVertex(v); vv.repaint(); } }); m.add(mi); // 3. "schedule" checkbox menu final JCheckBoxMenuItem mic = new JCheckBoxMenuItem("Schedule"); mic.setSelected(v.isSchedule); mic.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { v.isSchedule = mic.isSelected(); } }); m.add(mic); return m; } 上 述代 码可 分为 三个 部分 : 第 一步 从菜 单中 移除 所有 菜单 项, 以便 重新 构造 菜单 ; 第 二步 构造 “删 除当 前顶 点 ”菜 单; 顶点 的名 称从 参数 v的title 字 段中 得到 ; 第 三步 构造 复选 框式 的 “是 否属 于计 划项 目 ”菜 单; 如果 该顶 点是 计划 项目 (字 段 isSchedule 为真), 则复 选 框 初始 状态 为 “选中”。 这样,在PopupVertexEdgeMenuMousePlugin.java 的updateVertexMenu()方 法中 ,只 需要 加入 对上 面方 法的 调用 即 可: private void updateVertexMenu(V v, VisualizationViewer vv, Point2D point) { vertexPopup = VertexPopupMenu.update((Idea) v, vv, point); } Ex2.java 的 执行 结果 如图 13所 示。 正如 预料 的那 样, 第一 次点 击 mind 顶 点的 时候 ,弹 出菜 单中 Schedule 复选 框 未被 选中 ;点 击该 菜单 项后 ,则 第二 次弹 出菜 单时 该复 选框 是出 于选 中状 态。 另外 ,由 于我 们没 有为 边编 写 代 码, 因此 在边 上点 击鼠 标右 键将 抛出 异常 。 图13 上下文敏感的弹出菜单 14.14.14.14. 树 的实 现及 计算 树(Tree) 和森 林( Forrest) 是图 的特 例。 JUNG 为 这两 种数 据结 构提 供了 专用 的接 口以 实现 其相 关的 算法 。 以 DelegateTree 的 树实 现为 例, 这种 树直 接以 有向 图为 基础 实现 Tree 接 口, 其常 用的 函数 包括 : public boolean addVertex(V vertex): 添加 根节 点; public boolean addChild(E edge, V parent, V child): 添加 子节 点; public boolean addEdge(E e, V v1, V v2): 同上 。 下 述代 码将 构造 如图 14所 示的 树, 其中 的布 局为 TreeLayout: DelegateTree g = new DelegateTree(); g.addVertex("V0"); g.addEdge(edgeFactory.create(), "V0", "V1"); g.addEdge(edgeFactory.create(), "V0", "V2"); g.addEdge(edgeFactory.create(), "V1", "V4"); g.addEdge(edgeFactory.create(), "V2", "V3"); g.addEdge(edgeFactory.create(), "V2", "V5"); g.addEdge(edgeFactory.create(), "V4", "V6"); g.addEdge(edgeFactory.create(), "V4", "V7"); g.addEdge(edgeFactory.create(), "V3", "V8"); g.addEdge(edgeFactory.create(), "V6", "V9"); g.addEdge(edgeFactory.create(), "V4", "V10"); TreeLayout layout = new TreeLayout(g); ( 其它 代码 略) 图14 树及 TreeLayout 布局 15.15.15.15. 结语 JUNG 对图、树、森 林等 数据 结构 的计 算和 可视 化提 供 了完 整的 基础 支持 ,使 程序 员可 以从 繁杂 的底 层编 程中 解 放 出来 ,从 而大 大的 提高 相关 应用 软件 的实 现进 度; 同时 JUNG 在 图树 算法 、交 互支 持、 可视 化布 局等 方面 的 强 大功 能, 更可 以使 程序 员如 虎添 翼; 不足 的地 方是 ,其 API 的 文档 不够 齐全 ,有 些内 容甚 至严 重过 期, 因此 需 要参 考其 源码 (可 以在 Source Forge 下载)进 行工 作 。本 文对 JUNG 的 应用 编程 做了 较为 全面 的讲 解 ,并 对如 何 参考 源码 来实 现特 定的 功能 也做 了示 范。 http://jung.sourceforge.net/applet/index.html 中 更有 多个 内容 丰富 的样 例 可供 参考 ;源 码包 中同 时也 包含 这些 样例 的源 码, 可供 直接 学习 套用 。 -----------------
还剩18页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

yuerthe9

贡献于2011-02-25

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf