QT網絡拓撲圖繪制實驗

前言

在網絡通訊中,我qt常用的是TCP或者UDP協議,就比方說TCP吧,一臺服務器有時可能會和多臺客戶端相連接,我之前都是處理單鏈接情況,最近研究圖結構的時候,突然就想到了這個問題。那么如何解決這個問題呢,我是想將圖顯示在view中,并且可以動態交互。

圖的繪制API支持

首先就是圖的繪制了,c++的stl和qt封裝的庫對圖結構,都沒有直接的支持,無非是容器接適配器模擬鄰接表什么的實現,對我來說感覺好麻煩,我就想偷懶,上網搜了下,了解到了有兩個庫支持圖結構的繪制,一個是BOOST庫,這個不用介紹了,c++的一些新特性比如智能指針就是從這來的。再一個就是OGDF。

  • 圖結構與算法支持
    OGDF支持多種圖結構(如無向圖、有向圖、帶權圖等),并提供豐富的算法庫,包括:

    • 布局算法:如分層布局(Sugiyama Layout)、力導向布局(Force-Directed Layout)、樹狀布局(Tree Layout)等,用于優化節點和邊的空間排列。

    • 圖操作:支持圖的復制、子圖提取(如連通分量分離)、節點與邊的動態增刪等4。

    • 屬性管理:通過GraphAttributes類管理節點和邊的可視化屬性(如顏色、大小、標簽),需注意屬性與圖結構的同步問題。

  • 跨平臺與擴展性
    OGDF兼容Windows、Linux和macOS,支持與Qt等GUI框架集成,便于開發交互式圖形界面應用。

  • 高性能與模塊化設計
    其代碼高度優化,適用于大規模圖數據處理。用戶可通過繼承類或重載函數擴展功能,例如自定義布局算法或調整節點渲染邏輯。

與其他工具的對比

  • Boost Graph Library (BGL):BGL側重通用圖算法,而OGDF更專注于可視化與布局優化。

  • Graphviz:Graphviz適合快速生成靜態圖,OGDF則提供更靈活的API和動態交互支持,適合集成到C++應用中4。

圖的繪制?

采用力向布局繪制,即有鏈接的兩個節點會相互靠近。

首先引入庫函數
#include <ogdf/basic/Graph.h>
#include <ogdf/basic/GraphAttributes.h>

用Graph創建一個圖,通過newnode()創建節點newedge()創建邊,只包含圖的邏輯結構,不包含可視化的屬性。

用graphattributes創建節點屬性對象,用來存儲圖可視化或布局屬性
// 創建圖
Graph graph;
GraphAttributes ga(graph, GraphAttributes::nodeGraphics | GraphAttributes::edgeGraphics);
?添加節點

接下來開始在圖中加入需要的節點(服務器節點/客戶端節點)

// 添加服務器節點
node serverNode = graph.newNode();
ga.x(serverNode) = 0;  // 初始坐標
ga.y(serverNode) = 0;// 添加客戶端節點(示例:3個客戶端)
std::vector<node> clientNodes;
for (int i = 0; i < 3; ++i) {node client = graph.newNode();ga.x(client) = i * 50; // 臨時坐標,布局算法會覆蓋ga.y(client) = i * 50;clientNodes.push_back(client);graph.newEdge(serverNode, client); // 連接服務器與客戶端
}
選擇力導向布局,使服務器居中,客戶端均勻分布
#include <ogdf/energybased/FMMMLayout.h>FMMMLayout fmmm;
fmmm.useHighLevelOptions(true);// 啟用高級配置
fmmm.unitEdgeLength(100); // 控制節點間距
fmmm.newInitialPlacement(true);// 強制重新計算初始位置
fmmm.call(ga); // 應用布局算法,更新節點坐標
這樣圖的布局部分就完成了,接下來我們需要將繪制好的圖映射到view上。在qt中使用QGraphicsSceneQGraphicsView繪制節點和邊:(這里要注意一個問題,ogdf采用的是原始坐標系,即x軸從左往右,y軸從下往上遞增,而場景視圖的不同,他的y軸是從上往下遞增的,x軸一樣,所以在映射的過程中是需要翻轉Y軸坐標)
// 在Qt中創建場景和視圖
QGraphicsScene *scene = new QGraphicsScene;
QGraphicsView *view = new QGraphicsView(scene);// 繪制服務器節點(紅色圓形)
QGraphicsEllipseItem *serverItem = scene->addEllipse(ga.x(serverNode) - 20, ga.y(serverNode) - 20, 40, 40,QPen(Qt::black), QBrush(Qt::red)
);// 繪制客戶端節點(藍色圓形)和邊
for (node client : clientNodes) {// 客戶端節點QGraphicsEllipseItem *clientItem = scene->addEllipse(ga.x(v) - 20,     // 橢圓左上角的 X 坐標(中心點 X 減半徑)ga.y(v) - 20,     // 橢圓左上角的 Y 坐標(中心點 Y 減半徑)40,               // 橢圓的寬度(直徑)40,               // 橢圓的高度(直徑)QPen(Qt::black),  // 邊框畫筆(黑色,默認寬度 1)QBrush(Qt::blue)  // 填充畫刷(藍色));// 邊(服務器到客戶端)QLineF line(ga.x(serverNode), ga.y(serverNode), ga.x(client), ga.y(client));scene->addLine(line, QPen(Qt::gray, 2));
}view->show();
當客戶端連接或斷開時,更新OGDF圖并刷新布局,實現實時交互
// 添加新客戶端
void addClient() {node newClient = graph.newNode();graph.newEdge(serverNode, newClient);clientNodes.push_back(newClient);// 重新應用布局算法FMMMLayout fmmm;fmmm.call(ga);// 更新Qt場景updateQtScene();
}// 刪除客戶端
void removeClient(node client) {graph.delNode(client);auto it = std::find(clientNodes.begin(), clientNodes.end(), client);if (it != clientNodes.end()) clientNodes.erase(it);// 重新布局并刷新界面FMMMLayout fmmm;fmmm.call(ga);updateQtScene();
}// 刷新Qt圖形項
void updateQtScene() {scene->clear();// 重新繪制所有節點和邊(參考步驟4)
}

擴展應用:自定義交互

若需實現拖拽節點后更新布局,可結合 Qt 事件和 OGDF:

// 1. Qt 中捕獲節點拖拽事件
void MyGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {// 更新 OGDF 中的坐標ga.x(myNode) = event->pos().x();ga.y(myNode) = event->pos().y();
}// 2. 部分重新布局(需自定義算法)
void updateLayout() {// 固定已拖拽的節點,僅調整其他節點FMMMLayout fmmm;fmmm.fixSomeNodes({myNode});  // 假設支持固定節點fmmm.call(ga);
}

?將自定義節點屬性如IP地址與對應節點相綁定

有三種辦法:

方案一:使用外部映射表(推薦)

在?Qt 應用層?維護一個?std::map?或?QHash,將 OGDF 的節點對象映射到業務屬性:

// 定義節點業務數據類
struct NodeInfo {QString ip;QString name;// 其他業務字段...
};// 全局或類成員變量
std::map<ogdf::node, NodeInfo> nodeInfoMap;// 添加節點時綁定數據
ogdf::node clientNode = graph.newNode();
nodeInfoMap[clientNode] = NodeInfo{"192.168.1.2", "ClientA"};// 通過節點獲取數據(如在Qt點擊事件中)
void onNodeClicked(ogdf::node clickedNode) {if (nodeInfoMap.contains(clickedNode)) {qDebug() << "IP:" << nodeInfoMap[clickedNode].ip;}
}

優點

  • 數據與圖結構解耦,OGDF 更新(如刪除節點)時無需同步業務數據

  • 適用于業務屬性復雜或需頻繁增刪的場景


方案二:擴展?GraphAttributes(高級用法)

通過繼承?GraphAttributes?添加自定義屬性字段,但需修改 OGDF 源碼或自定義包裝類:

class CustomGraphAttributes : public ogdf::GraphAttributes {
public:// 添加自定義屬性QString& ip(ogdf::node v) { return m_nodeIP[v]; }private:// 使用 OGDF 的擴展機制存儲數據ogdf::NodeMap<QString> m_nodeIP;
};// 初始化時使用自定義類
CustomGraphAttributes ga(graph, GraphAttributes::nodeGraphics);
ga.ip(serverNode) = "192.168.1.1";
缺點
  • 需要深入理解 OGDF 內部機制,對新手不友好

  • 修改 OGDF 源碼可能導致版本升級沖突


方案三:Qt 圖形項存儲(簡單場景)

將業務數據直接附加到?QGraphicsItem?的自定義數據中:

// 創建節點圖形項時存儲數據
QGraphicsEllipseItem* clientItem = scene->addEllipse(...);
clientItem->setData(Qt::UserRole, QVariant::fromValue(NodeInfo{"192.168.1.2", "ClientA"}));// 點擊時獲取數據
void mousePressEvent(QGraphicsSceneMouseEvent* event) {QGraphicsItem* item = scene->itemAt(event->scenePos(), QTransform());if (item) {NodeInfo info = item->data(Qt::UserRole).value<NodeInfo>();qDebug() << "IP:" << info.ip;}
}

缺點

  • 數據與圖形項綁定,若 OGDF 節點被刪除但 Qt 項未及時清理,會導致數據殘留

  • 不適合需要基于業務屬性進行圖算法計算的場景(如按 IP 過濾節點)

?新的問題

到上面圖就基本繪制完成了,但是我遇到了一個新的問題,如果鏈接的節點太多了,場景視圖裝不下怎么辦

  • ?解決思路
    1. ?計算當前布局的坐標范圍?(找到所有節點的最小/最大坐標)。
    2. ?將原始坐標歸一化?(縮放到?[0, 1]?區間)。
    3. ?按目標尺寸縮放并平移,使布局適配到指定區域(如?800x600?的 Qt 場景)。

具體步驟:

1.獲取布局的邊界范圍

  • minX:所有節點中,?最小的 x 坐標值**?(最左側節點的位置)。
  • ?**maxX:所有節點中,?最大的 x 坐標值**?(最右側節點的位置)。
  • ?**minY:所有節點中,?最小的 y 坐標值**?(最下方節點的位置)。
  • ?**maxY:所有節點中,?最大的 y 坐標值**?(最上方節點的位置)。
  • 假設節點坐標分布在?x ∈ [50, 950]y ∈ [30, 570]
  • 則?minX=50,?maxX=950,?minY=30,?maxY=570
double minX = std::numeric_limits<double>::max();
double maxX = -minX;
double minY = minX, maxY = maxX;for (node v : graph.nodes) {minX = std::min(minX, ga.x(v));maxX = std::max(maxX, ga.x(v));minY = std::min(minY, ga.y(v));maxY = std::max(maxY, ga.y(v));
}

?先初始化極端值,再把繪制好的節點數據依次遍歷比較,比如ga(x,y)節點,min(minX,x),把極值與節點的x比較,取最小的作為新的最小x值,其他同理。

把minx初始化為極小負數,maxx初始化為極大正數,與加入的節點坐標相比對,第一次加入的節點的x初始化minx,后面加入的節點x與minX,maxX比較,比minx小,更新minX,比maxX大,更新MaxX。

?2.計算縮放比例和目標區域

qt界面上的布局如上,view是我們顯示的區域,他的x范圍是場景的x范圍減去兩邊的margin得到,

maxX-maxY得到繪制的范圍,用目標的范圍除以繪制的范圍就可以得到縮放比例,取x的比例和y的比例最小,保證x,y都唔那個縮小進目標。實現代碼如下:

double targetWidth = 800.0;
double targetHeight = 600.0;
double scaleX = (targetWidth - 2 * margin) / (maxX - minX);
double scaleY = (targetHeight - 2 * margin) / (maxY - minY);
double scale = std::min(scaleX, scaleY); // 保持寬高比

?3.進行縮放和偏移

我們計算好了縮放比例,下一步開始縮放并放到view中,注意要加個margin,有個邊框的

for (node v : graph.nodes) {ga.x(v) = (ga.x(v) - minX) * scale + margin;ga.y(v) = (ga.y(v) - minY) * scale + margin;
}

這樣就解決了邊界溢出的問題,這是其中一種方法,網上面還有動態調整場景范圍,QT自動適配fitInView,OGDF封裝的布局包裝類LayoutPlanarizationGrid

// 計算所有圖元的邊界矩形
QRectF itemsBoundingRect = scene.itemsBoundingRect();// 調整視圖,使所有內容可見
view.fitInView(itemsBoundingRect, Qt::KeepAspectRatio);

?或

PlanarizationGridLayout pgl;
pgl.setPageRatio(1.0);       // 設置寬高比
pgl.setMinimalNodeDistance(20);
pgl.call(ga);

依據情況選用。

這樣之前想到的問題就解決了,各位如果有什么新的想法或者建議歡迎告訴我本人作品永久開源,希望志同道合的網友一起學習建設。如果覺得寫的可以記得一件三連哦。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77580.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77580.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77580.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

DNS主從同步實驗

dns域名解析原理 實驗步驟1、主dns要完成dns解析&#xff1a;192.168.21.128 [rootlocalhost ~]# yum install bind -y [rootlocalhost ~]# systemctl start named [rootlocalhost ~]# vim /etc/named.conf options { listen-on port 53 { any; }; direct…

知識了解03——怎么解決使用npm包下載慢的問題?

1、為什么使用npm下載包會下載的慢 因為使用npm下載包時&#xff0c;默認使用國外服務器進行下載&#xff0c;此時的網絡傳輸需要經過漫長的海底電纜&#xff0c;因此下載速度會變慢 2、怎么解決&#xff1f;&#xff08;切換鏡像源&#xff09; &#xff08;1&#xff09;方…

在Ubuntu系統中安裝和升級RabbitVCS

在Ubuntu系統中安裝和升級RabbitVCS 目前在ubuntu中使用svn的GUI工具&#xff0c;已經安裝了。想升級一下。 當前遇到的問題是&#xff0c;我想用它看看我當前的代碼對應的版本號&#xff0c;然后再決定是否update。但是&#xff0c;好像我看不出來。根本不如在windows使用To…

cv::dnn::NMSBoxes和nms-free的比較

1. 原理與目標 cv::dnn::NMSBoxes 基于傳統的非極大值抑制&#xff08;NMS&#xff09;算法&#xff0c;通過交并比&#xff08;IoU&#xff09;篩選重疊框&#xff0c;保留置信度最高的框&#xff0c;抑制冗余檢測。支持變體如 Soft-NMS&#xff08;通過降低分數而非直接抑制&…

React-useImperativeHandle (forwardRef)

我們會遇到這樣的場景&#xff1a;某個組件想要暴露一些方法&#xff0c;來供外部組件來調用。例如我們在開發form表單的時候&#xff0c;就需要把設置表單值、重置值、提交等方法暴露給外部使用。會有如下代碼&#xff1a; import { forwardRef } from react;const Form for…

多人五子棋聯機對戰平臺 測試報告

目錄 項目介紹 測試用例設計 部分功能測試示例 自動化測試 測試范圍 排除范圍 自動化測試目錄?編輯 執行全部自動化測試用例 性能說明 總結 性能測試 結果分析 測試總結 項目介紹 該項目基于WebSocket實現實時通信&#xff0c;采用SSM框架構建在線五子棋多人聯機…

JAVAEE(網絡原理—UDP報頭結構)

我們本篇文章要講的是UDP的報頭結構以及注意事項。 下面呢&#xff0c;我先說一下UDP是什么&#xff1f; 1.UDP是什么&#xff1f; UDP是一種網絡協議。網絡協議是計算機網絡中&#xff0c;為了使不同設備之間能夠準確、高效地進行數據交換和通信&#xff0c;而預先制定的一…

STM32學習筆記匯總

所有學習資料均參考b站江科大&#xff0c;和鐵山羊 一.創建工程&#xff08;比較麻煩&#xff0c;而且時間長了就容易忘記&#xff09; 二.點燈大師&#xff08;成功的第一步&#xff09; 三.不同的燒錄器使用&#xff08;Jlink-stlink&#xff09;

【MySQL】SQL語句在MySQL中的執行過程?主要存儲引擎區別?

MySQL SQL語句執行過程詳解 作為面試官&#xff0c;我來詳細剖析一條SQL語句在MySQL中的完整執行過程&#xff0c;這是每個后端開發者都應該掌握的核心知識。 一、連接階段 建立連接 客戶端通過TCP/IP協議與MySQL服務器建立連接(默認3306端口)服務器驗證用戶名、密碼和權限…

【記錄】服務器安裝ffmpeg

前言 因為項目中需要用到 ffmpeg 進行圖像的一些操作,本文記錄下在服務器安裝 ffmpeg 的全過程,還是具有一定挑戰性的。 系統詳情 本文使用的操作系統詳情如下 通過 命令 cat /etc/os-release 獲取 雖然操作系統為 Rocky Linux,但安裝過程是通用的,因為本文記錄的是從源代碼…

Django之modelform使用

Django新增修改數據功能優化 目錄 1.新增數據功能優化 2.修改數據功能優化 在我們做數據優化處理之前, 我們先回顧下傳統的寫法, 是如何實現增加修改的。 我們需要在templates里面新建前端的頁面, 需要有新增還要刪除, 比如說員工數據的新增, 那需要有很多個輸入框, 那html…

HTML5 應用程序緩存:原理、實踐與演進

在 Web 技術的發展歷程中&#xff0c;HTML5 引入的應用程序緩存&#xff08;Application Cache&#xff09;曾是提升 Web 應用離線體驗的重要技術。它允許 Web 應用進行緩存&#xff0c;使用戶在沒有因特網連接時也能訪問應用&#xff0c;為 Web 應用帶來了顯著的優勢。然而&am…

【問題筆記】解決python虛擬環境運行腳本無法激活問題

【問題筆記】解決python虛擬環境運行腳本無法激活問題 錯誤提示問題所在解決方法**方法 1&#xff1a;臨時更改執行策略****方法 2&#xff1a;永久更改執行策略** **完整流程示例** 錯誤提示 PS F:\PythonProject\0419graphrag-local-ollama-main> venv1\Scripts\activate…

解決echarts餅圖label顯示不全的問題

解決辦法 添加如下配置&#xff1a; labelLayout: {hideOverlap: false},

Pandas數據合并與重塑

在數據處理與分析的領域中&#xff0c;Pandas 無疑是一顆璀璨的明星。它提供了豐富且強大的功能&#xff0c;讓我們能夠輕松應對各種復雜的數據操作。其中&#xff0c;數據合并與重塑是兩個至關重要的環節&#xff0c;它們能夠幫助我們整合不同來源的數據&#xff0c;調整數據的…

Nodejs數據庫單一連接模式和連接池模式的概述及寫法

概述 單一連接模式和連接池模式是數據庫連接的兩種主要方式&#xff1a; 單一連接模式&#xff1a; 優點&#xff1a;實現簡單&#xff0c;適合小型應用缺點&#xff1a;每次請求都需要創建新連接&#xff0c;連接創建和銷毀開銷大&#xff0c;并發性能差&#xff0c;容易出…

將 DeepSeek 集成到 Spring Boot 項目實現通過 AI 對話方式操作后臺數據

文章目錄 項目簡介GiteeMCP 簡介環境要求項目代碼核心實現代碼MCP 服務端&#xff08;批量注冊 Tool&#xff09;MCP 客戶端&#xff08;調用 DeepSeek&#xff09; DeepSeek APIDockersse 連接ws 連接&#xff08;推薦&#xff09;http 連接 Cherry Studio配置模型配置 MCP調用…

【HDFS入門】HDFS性能調優實戰:壓縮與編碼技術深度解析

目錄 1 HDFS性能調優概述 2 HDFS壓縮技術原理與應用 2.1 常見壓縮算法比較 2.2 壓縮流程架構 2.3 壓縮配置實踐 3 列式存儲編碼技術 3.1 ORC與Parquet對比 3.2 ORC文件結構 3.3 Parquet編碼流程 4 性能調優實戰建議 4.1 壓縮選擇策略 4.2 編碼優化技巧 5 性能測試…

HCIP --- OSPF綜合實驗

一、拓撲圖 二、實驗要求 1&#xff0c;R5為ISP&#xff0c;其上只能配置IP地址;R4作為企業邊界路由器&#xff0c;出口公網地址需要通過PPP協議獲取&#xff0c;并進行chap認證。 2&#xff0c;整個0SPF環境IP基于172.16.0.8/16劃分。 3&#xff0c;所有設備均可訪問R5的環…

c++:線程(std::thread)

目錄 從第一性原理出發&#xff1a;為什么需要線程&#xff1f; ? 本質定義&#xff1a; &#x1f4cc; 使用基本語法&#xff1a; 線程之間的“并發”與“并行”的區別 線程安全與數據競爭&#xff08;Race Condition&#xff09; 如何讓線程“安全地”訪問數據&#x…