最近業餘在做一個基於結點的編輯工具玩, 遇到一個問題, 就是結點和連線多了, 經常會出現重疊交叉的問題, 導致圖看不清楚:
要是這個樣子, 還不如不用圖清楚呢, 所心就需要找一個方法來進行自動布局, 理想情況是這樣的(手動整理結果):
當然, 手動整理的話, 每個人弄出來的結果都不一樣. 自動的演算法肯定沒有100%完美的, 但是總是能方便不少的
在google了一會兒後, 發現這種結點-線組成的圖是一有個學名的: directed acyclic graph, 例如這樣:
無非我這個圖結點上的連接點是有限制的, 但這個對於布局演算法來說, 影響不大. 因為布局只需要大體考慮每個結點的位置
那麼, 這個演算法需要滿足幾個條件:
- 結點之間不能有重疊
- 連線之間盡量減少交差
- 結點之間是有基本的層次關係對齊的
基於這些限制條件, google到一個比較有名的演算法Sugiyama's layout algorithm初步看了一上, 這個演算法比較複雜, 是多種演算法的集合自己不是很熟悉這方面的理論知識, 所以還是決定採用第三的演算法庫C++可以使用的圖繪製演算法庫, 比較常見的有Graphviz, OGDF, Boost Graph根據這個問題(http://stackoverflow.com/questions/2751826/which-c-graph-library-should-i-use)的推薦, 嘗試了OGDF, 效果還不錯(可惜是GPL協議)
//------------------------------------------------------------------------------voidQNodesEditor::autoLayout(){using namespace ogdf;Graph graph;// setup graphQMap<NodeElement*, QNEBlock*> nodeMap;foreach(QGraphicsItem * item, scene->items()){if (item->type() == QNEBlock::Type){NodeElement* node = graph.newNode();item->setData(QNEBlock::Type, qVariantFromValue((void*)node));nodeMap[node] = (QNEBlock*)item;}}foreach(QGraphicsItem * item, scene->items()){if (item->type() == QNEConnection::Type){QNEConnection* connection = (QNEConnection*)item;NodeElement* node1 = (NodeElement*)connection->port1()->block()->data(QNEBlock::Type).value<void*>();NodeElement* node2 = (NodeElement*)connection->port2()->block()->data(QNEBlock::Type).value<void*>();graph.newEdge(node1, node2);}}// node sizeGraphAttributes graphAttr(graph, GraphAttributes::nodeGraphics | GraphAttributes::edgeGraphics | GraphAttributes::nodeLabel | GraphAttributes::nodeColor | GraphAttributes::edgeColor | GraphAttributes::edgeStyle | GraphAttributes::nodeStyle | GraphAttributes::nodeTemplate);NodeElement* node;forall_nodes(node, graph){QNEBlock* item = nodeMap[node];graphAttr.width(node) = item->getHeight();graphAttr.height(node) = item->getWidth();}// compute layoutSugiyamaLayout layout;FastHierarchyLayout* ohl = new FastHierarchyLayout;ohl->layerDistance(30);ohl->nodeDistance(25);layout.setLayout(ohl);layout.call(graphAttr);// update node positionforall_nodes(node, graph){double x = graphAttr.x(node);double y = graphAttr.y(node);QNEBlock* item = nodeMap[node];item->setPos(y, x);}}
最終效果: