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=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.mzph.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…