如何让GraphStream仅渲染变更元素 优化大规模图动画性能
问题描述
我使用Graphstream创建了表示某区域道路地图的图结构,效果如下图所示:
我需要实现蓝色节点在图上移动的动画效果,因此将图放在独立线程中展示,每隔固定时间将不同节点标记为蓝色,核心实现代码如下:
public void drawGraph(List<MyCustomNodeClass> nodes, List<MyCustomEdgeClass> edges){ Graph graph = new MultiGraph("main graph"); Viewer viewer = new Viewer(graph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD); ViewerPipe pipeIn = viewer.newViewerPipe(); viewer.addView("view1", new J2DGraphRenderer()); pipeIn.addAttributeSink(graph); pipeIn.pump(); nodes.forEach(node-> graph.addNode(node.getId())); edges.forEach(edge-> graph.getEdge(edge.getId(), edge.getSrc(), edge.getDest())); Node node = graph.getNode("start"); while(true){ pipeIn.pump(); sleep(100); node.setAttribute("ui.style", "z-index: 1; size: 3px; fill-color: black;"); node = node = node.getNeighborNodeIterator().next();; node.setAttribute("ui.style", "z-index: 2; size: 10px; fill-color: blue;"); } }
private void sleep(long millis ) { try { Thread.sleep( millis ) ; } catch (InterruptedException e) { e.printStackTrace(); } }
注:原sleep方法参数名标注为seconds实际传入100,是毫秒级休眠,属于注释笔误
当前图规模极大,共包含15997条边、9084个节点,GraphStream默认全图重绘的耗时超过了间隔时间,动画卡顿严重,需要实现仅渲染变更对象(本场景下仅2个节点状态变化)的效果。
解决方法
通过配置内置渲染开关+优化样式逻辑即可实现局部增量渲染,不需要修改GraphStream源码:
- 开启脏矩形渲染模式,关闭全量重绘
在初始化Viewer之前添加两个JVM参数配置,GraphStream默认的Java2D渲染器会自动切换为局部渲染:
配置生效后,渲染器不会每帧遍历所有节点和边,只会重绘属性发生变化的元素所在的屏幕区域,针对万级规模的静态路网,渲染耗时会降到1ms以内,完全满足100ms间隔的动画需求。// 仅重绘元素变更所在的矩形区域,不重绘全图 System.setProperty("org.graphstream.renderer.dirtyRectangles", "true"); // 关闭每帧强制重绘全图的默认逻辑 System.setProperty("org.graphstream.renderer.repaintAll", "false"); - 优化样式设置逻辑,减少不必要的属性变更开销
不要在循环里给单个节点反复设置全量内联样式,改用全局样式表+类切换的方式:- 初始化图之后只设置一次全局样式:
graph.setAttribute("ui.stylesheet", "node { z-index:1; size:3px; fill-color:black; }" + "node.active { z-index:2; size:10px; fill-color:blue; }" + "edge { fill-color:#ccc; }" );- 动画循环里只需要切换节点的样式类,不需要重复写入全量样式属性:
// 初始化第一个高亮节点 Node currentNode = graph.getNode("start"); currentNode.setAttribute("ui.class", "active"); while(true){ pipeIn.pump(); sleep(100); // 取消上一个节点的高亮 currentNode.removeAttribute("ui.class"); // 移动到下一个节点 currentNode = currentNode.getNeighborNodeIterator().next(); // 高亮当前节点 currentNode.setAttribute("ui.class", "active"); } - 额外修正:现有代码里添加边的逻辑有笔误,
graph.getEdge()是获取已存在的边,不会新增边,需要改成graph.addEdge(edge.getId(), edge.getSrc(), edge.getDest())才能正确把边加入图结构。如果路网结构初始化后不会再改动,可以在所有节点、边添加完成后调用graph.freeze()冻结图结构,渲染器会跳过结构遍历的逻辑,性能还能再提升20%左右。
内容的提问来源于stack exchange,提问作者Kfir Ettinger
相关产品推荐
相关产品推荐

