QT网络拓扑图绘制实验

news2025/5/9 17:42:48

前言

在网络通讯中,我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=50maxX=950minY=30maxY=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.coloradmin.cn/o/2338792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

支持中文对齐的命令行表格打印python库——tableprint

文章目录 快速入门 还在为表格中含有中文&#xff0c;命令行打印无法对齐而苦恼吗&#xff1f; 还在为冗长的数据添加代码而抓狂吗&#xff1f; tableprint来了&#xff01;&#xff01;&#xff01;&#xff0c;它完美的解决了上述两个问题&#xff0c;快来试试吧&#xff01;…

从《周游记3》演绎歌剧版《菊花台》,周杰伦婚礼曲目意大利文版惊喜亮相

今天&#xff08;4月19日&#xff09;22:00&#xff0c;由魔胴西西里咖啡冠名的户外实境互动综艺《周游记3》第四期即将播出。本期节目中&#xff0c;“J式之旅”发起人周杰伦和林暐恒、杜国璋、陈冠霖、陈冠廷&#xff0c;将继续意大利之旅&#xff0c;从那不勒斯的百年老店到…

生物化学笔记:医学免疫学原理23 免疫检查点分子与肿瘤免疫治疗(PD-1抑制剂黑色素瘤)

免疫检查点分子与肿瘤免疫治疗 免疫检查点分子与肿瘤免疫治疗-2

CasualLanguage Model和Seq2Seq模型的区别

**问题1&#xff1a;**Causal Language Modeling 和 Conditional Generation 、Sequence Classification 的区别是什么&#xff1f; 因果语言模型(Causal Language Model)&#xff1a; 预测给定文本序列中的下一个字符&#xff0c;一般用于文本生成、补全句子等&#xff0c;模型…

verilog float mult

module pipe_float_mul(input wire clk ,// 时钟信号input wire en ,// 使能信号input wire rst_n ,// 复位信号input wire round_cfg ,// 决…

微信小程序调用yolo目标检测模型

目录 后端 前端微信小程序 完整代码 后端 利用Flask&#xff0c;调用目标检测模型&#xff0c;后端代码如下。 # flask_yolo.py from flask import Flask, request, jsonify from ultralytics import YOLO from PIL import Imageapp Flask(__name__) model_path best.p…

Flink框架十大应用场景

Flink框架适合应用的场景 1. 流式数据处理 Flink框架最常用的应用场景是流式数据处理。流式数据处理是指对实时数据进行处理,以便及时地做出决策。例如,一个电商网站需要对用户的行为进行实时分析,以便根据用户的兴趣和行为推荐商品。Flink框架可以帮助电商网站实时地处理数…

【android telecom 框架分析 01】【基本介绍 2】【BluetoothPhoneService为何没有源码实现】

1. 背景 我们会在很多资料上看到 BluetoothPhoneService 类&#xff0c;但是我们在实际 aosp 中确找不到具体的实现&#xff0c; 这是为何&#xff1f; 这是一个很好的问题&#xff01;虽然在车载蓝牙电话场景中我们经常提到类似 BluetoothPhoneService 的概念&#xff0c;但…

【Harmony】文本公共接口EditMenuOptions的使用

文章目录 一、EditMenuOptions介绍二、相关接口介绍2.1、editMenuOptions2.2、EditMenuOptionsonCreateMenu函数说明onMenuItemClick函数说明 2.3、TextRange对象说明2.4、TextMenuItem对象说明2.5、TextMenuItemId属性ofequals 三、简单案例 一、EditMenuOptions介绍 EditMen…

《软件设计师》复习笔记(14.1)——面向对象基本概念、分析设计测试

目录 一、面向对象基本概念 对象&#xff08;Object&#xff09; 类&#xff08;Class&#xff09; 抽象&#xff08;Abstraction&#xff09; 封装&#xff08;Encapsulation&#xff09; 继承&#xff08;Inheritance&#xff09; 多态&#xff08;Polymorphism&#…

JS中实现类似sleep、wait、delay的延时功能

前言 编写代码时很多时候需要进行流程化的操作&#xff0c;各个流程间通常需要等待一定时间&#xff0c;这在很多语言中通常可以使用 sleep 、 wait 、 delay 等函数来实现。JavaScript原生并没有类似的功能&#xff0c;想要延时通常就是使用 setTimeout(functionRef, delay) …

Banana Pi BPI-RV2 RISC-V 路由器开发板发售, 全球首款RISC-V路由器

Banana Pi BPI-RV2 开源路由器是矽昌通信和⾹蕉派开源社区&#xff08;Banana Pi &#xff09;合作设计, 联合打造全球首款RISC-V架构路由器开发板。 这是香蕉派开源社区与矽昌通信继BPI-Wifi5 低成本Wifi5 路由器合作之后的又一力作&#xff0c;为全球开发者与商业客户提供基于…

MAUI项目iOS应用以进 App Store 分发

目录 一.通过Visual Studio分发应用1. 登录Apple 开发者帐户到 Visual Studio2.创建分发证书和配置文件3. 分发应用4. 在App Store Connect 中创建应用程序记录5. 如果你想使用mac发布应用 一.通过Visual Studio分发应用 1. 登录Apple 开发者帐户到 Visual Studio 首先我们要…

CentOS 7系统yum报错解决方案(CentOS 7官方EOL问题修复)

摘要 解决CentOS 7因EOL导致的yum update报错问题&#xff0c;通过替换阿里云镜像源恢复软件安装功能&#xff0c;包含详细操作步骤、操作截图、验证方法与备选镜像源&#xff0c;附有安全风险提示。 一、故障现象与原因分析 1.1 典型报错信息 # 执行yum命令时出现&#xff…

解决Windows update服务启动拒绝访问的问题 | wuauserv 注册表拒绝访问的方法

在某些情况下,为了配置系统更新相关服务(例如禁用 Windows 自动更新),我们需要更改注册表中 wuauserv 项的权限。本教程将带你一步步操作,成功获取并修改权限。 修改注册表路径: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\wuauserv 步骤一:打开注册表编辑…

深入解析 JDK jstack 命令:线程分析的利器

你点赞了吗&#xff1f;你关注了吗&#xff1f;每天分享干货好文。 高并发解决方案与架构设计。 海量数据存储和性能优化。 通用框架/组件设计与封装。 如何设计合适的技术架构&#xff1f; 如何成功转型架构设计与技术管理&#xff1f; 在竞争激烈的大环境下&#xff0c…

【操作系统原理03】处理机调度与死锁

文章目录 大纲一.处理机调度概念与层次0.大纲1.基本概念2.三个层次3.七状态模型4.三层调度都对比与联系 二.进程调度的时机&#xff0c;切换与过程的调度方式0.大纲1.进程调度时机2.调度方式3.进程的切换与过程 三.调度器和闲逛资源1.调度器/调度程序2.闲逛进程 四.调度算法的评…

Quipus,LightRag的Go版本的实现

1 项目简介 奇谱系统当前版本以知识库为核心&#xff0c;基于知识库可以快构建自己的问答系统。知识库的Rag模块的构建算法是参考了LightRag的算法流程的Go版本优化实现&#xff0c;它可以帮助你快速、准确地构建自己的知识库&#xff0c;搭建属于自己的AI智能助手。与当前LLM…

使用 Vite 快速搭建现代化 React 开发环境

1.检查环境 说明&#xff1a;检测环境&#xff0c;node版本为18.20.6。 2.创建命令 说明&#xff1a;创建命令&#xff0c;选择对应的选项。 npm create vitelatest 3.安装依赖 说明&#xff1a;安装相关依赖。 npm i

PG数据库推进医疗AI向量搜索优化路径研究(2025年3月修订版)

PG数据库推进医疗AI向量搜索优化路径研究 一、医疗 AI 向量搜索的发展现状与挑战 1.1 医疗数据特征与检索需求 医疗数据作为推动医疗领域进步与创新的关键要素,具有鲜明且复杂的特征。从多模态角度看,医疗数据涵盖了结构化数据,如患者基本信息、检验检查报告中的数值结果;…