图拓扑关系可视化的qt实现

前言

最近在做数据可视化的相关工作,包括曲线图,航迹图,图结构,树结构等。其中树结构相关的工作笔者以前曾经做过,可以参考笔者以前的博客。qt自定义树形控件之一qt自定义树形控件之二,当时还用数据库对树结构进行持久化。所以这几天的重心在图结构和曲线图上。本文主要介绍图结构的可视化,后续做曲线图进行介绍。

实现效果

在这里插入图片描述

代码实现

关于图结构的可视化,qt自带一个例子,叫做Elastic Nodes Example。里面有边和节点的绘图实现以及用边连接两个节点。本文内容基于此实现。
首先是node.h的实现,为了使任意两个节点的连线都不穿过其他节点,需要使所有节点均布在一个圆上。

double interval = 2*3.1416/graph->nodeNumber();
qreal xt = qSin(interval*m_node_id)*widgetw;
qreal yt = qCos(interval*m_node_id)*widgeth;
setPos(xt,yt);

同时在calculateForces函数中实现动态,在鼠标拖动一个节点后,该节点可以回到原来的位置。

void Node::calculateForces()
{
    //将所有节点固定在一个圆上
    qreal xvel = 0;
    qreal yvel = 0;

    double interval = 2*3.1416/graph->nodeNumber();

    qreal xt = qSin(interval*m_node_id)*widgetw;
    qreal yt = qCos(interval*m_node_id)*widgeth;
    xvel =( xt - pos().rx())/10;
    yvel =( yt - pos().ry())/10;

    if (qAbs(xvel) < 0.1 && qAbs(yvel) < 0.1)
        return;

    QRectF sceneRect = scene()->sceneRect();
    newPos = pos() + QPointF(xvel, yvel);
    newPos.setX(qMin(qMax(newPos.x(), sceneRect.left() + 10), sceneRect.right() - 10));
    newPos.setY(qMin(qMax(newPos.y(), sceneRect.top() + 10), sceneRect.bottom() - 10));
}

同时为了使在节点上显示编号,在painter函数最后添加

painter->drawText(QPointF(-4,4),QString::number(m_node_id));

m_node_id作为node类构造函数的一个参数。

画出的6个点如下图所示

在这里插入图片描述
在painter函数中,根据节点是否选中进行着色。

if(!is_choosed)
{
    gradient.setColorAt(1, QColor(Qt::yellow).lighter(120));
    gradient.setColorAt(0, QColor(Qt::yellow).lighter(120));
}
else
{
    gradient.setColorAt(1, QColor(Qt::green).lighter(120));
    gradient.setColorAt(0, QColor(Qt::green).lighter(120));
}

在mousePressEvent函数中,添加

graph->choosenode(m_node_id);

m_node_id是node类构造函数的一个参数,代表这个node的编号。这样GraphWidget类就能知道是哪个节点被选中了.
choosenode的实现如下:

void GraphWidget::choosenode(int node_id)
{
    if(is_waitlink == false)  //记录第一个点
    {
        firstnode =  node_id;
        node[firstnode]->setchoosed(true);
        node[firstnode]->update();
        is_waitlink = true;
    }
    else //判断和第一个点的关系
    {
        node[firstnode]->setchoosed(false);   //不起作用
        node[firstnode]->update();   //需要加上这一句
        if(node_id != firstnode)
        {
            int maxid = qMax(node_id,firstnode);
            int minid = qMin(node_id,firstnode);
            int edge_id = (18-minid)*(minid+1)/2+maxid-10;
            if(!edge[edge_id])  //指针需要初始化,野指针和空指针的区别
            {
                edge[edge_id] = new Edge(node[minid], node[maxid]);
                myscene->addItem(edge[edge_id]);
            }
            else
            {
                myscene->removeItem(edge[edge_id]);
                edge[edge_id] = NULL;
                node[firstnode]->setchoosed(false);
            }
        }
        is_waitlink = false;
    }
}

该函数的功能是实现依次选择的两个节点的连接和断开。当选择第一个节点时,把这个节点修改了绿色,

扫描二维码关注公众号,回复: 10396484 查看本文章

在这里插入图片描述
在选择第二个节点时,把前一个选择的节点改回黄色,同时把这两个节点连接起来,如果这两个节点本来是连接的,则把这两个节点断开。

在这里插入图片描述
这里面使用了一个数学技巧,如果要快速的存储和读取边,通常的想法是放到一个二维数组中,但是edge[3][5]和edge[5][3]会重复。这里使用一维数组存储边。在本例中,假定最多只能有10个节点,那么最多只能有45(C(10,2))条边。假定两个节点编号为x,y,且x<y, 那么边的编号为(18-x)*(x+1)/2+y-10可以做到不重不漏(其实是一个等差数列求和的问题)。
后续可以进行其他节点连接
在这里插入图片描述

发布了113 篇原创文章 · 获赞 132 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/iamqianrenzhan/article/details/105257745