Qt下使用QPainter实现界面上饼状图、圆环图的绘制

文章目录

  • 前言
  • 一、示例讲解
  • 二、圆环图绘制步骤
  • 三、设置圆环图数据
  • 四、示例完整代码
  • 五、下载链接
  • 总结


前言

前面的文章有讲述使用Qt下的Charts 模块来进行饼图的绘制:QChart实现ui界面上指定位置饼状图、圆环图的绘制,但是使用过程中并不能很好的实现自己想要的效果,而Qt中的绘图事件可以解决这个问题,所以决定使用QPainter及QPaintEvent来实现饼状图及圆环图的自定义绘制,在这里编写了一个简单的示例,并将相关代码展现出来以便大家学习,如有错误之处,欢迎大家批评指正。

项目效果
请添加图片描述


提示:以下是本篇文章正文内容,下面案例可供参考

一、示例讲解

首先添加相关头文件:

#include <QtMath>   //后文中计算文本位置使用
#include <QPainter>

类中添加paintEvent函数,并在cpp中进行重写(见后文)

protected:
    void paintEvent(QPaintEvent *);

本示例中圆环图各区域数据结构体如下,其中有区域的名称颜色及数量:

struct PieData
{
    QString name;   //名称
    int num;        //数量
    QColor color;   //颜色

    PieData(QString name,int num,QColor color)
    {
        this->name = name;
        this->num = num;
        this->color = color;
    }
};

圆环图各参数:

int m_radius;         //外圆半径
int m_innerWidth;     //圆环内径
QPoint m_center;      //圆心坐标
qreal m_startAngle;   //圆环绘制起点
int m_textDistance;   //文本与圆心的距离
qreal m_totality;     //总数
QVector<PieData> m_vData;   //数据容器

示例函数接口:
请添加图片描述

二、圆环图绘制步骤

1.使用drawRoundedRect实现饼状图圆角背景的绘制
2.获取各区域的占比*360得到此区域覆盖角度,使用drawPie完成各区域的绘制,绘制方向为逆时针
3.进行区域名称和数量文本的绘制,并使用drawLine将文本与对应区域的边界进行连接,所以这里要确定连接线的起点和终点位置
4.使用drawPoint来绘制连接线的终点,如果需要将终点设为空心圆,就需要使用drawEllipse组合实现
5.进行内圆的绘制,将饼图变为圆环图,详细的步骤见下列代码:

//重写绘图事件
void Widget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing,true);   //抗锯齿

    //绘制圆角背景
    painter.setBrush(Qt::white);
    painter.setPen(Qt::NoPen);   //去除背景边框
    painter.drawRoundedRect(10,10,360,360,8,8);

    //绘制饼图
    qreal startAngle = m_startAngle;   //绘制起点
    qreal spanAngle = 0;   //各区域占比,覆盖角度
    for(int i=0;i<m_vData.size();i++)
    {
        painter.setPen(m_vData[i].color);
        painter.setBrush(m_vData[i].color);
        if(m_totality)   //防止总数为0
        {
            spanAngle = m_vData[i].num * 360 / m_totality;
        }
        painter.drawPie(m_center.x() - m_radius,m_center.y() - m_radius,m_radius * 2,m_radius * 2,startAngle * 16,spanAngle * 16);
        startAngle += spanAngle;
    }

    //绘制区域名称和占比
    startAngle = m_startAngle;
    spanAngle = 0;
    for(int i=0;i<m_vData.size();i++)
    {
        painter.setPen(QColor("#333333"));
        painter.setFont(QFont("STSongti-SC-Bold, STSongti-SC",16));
        if(m_totality)
        {
            spanAngle = m_vData[i].num * 360 / m_totality;
        }
        int textAngle = startAngle + spanAngle / 2;
        QString text = QString("%1").arg(m_vData[i].name);
        int textWidth = painter.fontMetrics().horizontalAdvance(text);
        int textHeight = painter.fontMetrics().height();
        int textX = m_center.x() + m_textDistance * qCos(textAngle * M_PI / 180) - textWidth / 2;
        int textY = m_center.y() - m_textDistance * qSin(textAngle * M_PI / 180) + textHeight / 2;
        startAngle += spanAngle;

        //绘制文本
        QRect rect(textX,textY - textHeight,textWidth + 10,textHeight * 2);
        painter.drawText(rect,Qt::AlignCenter,text);

        //绘制连接线,文本要靠近对应区域,需要修改连接线终点位置
        painter.setPen(m_vData[i].color);
        int lineStartX = m_center.x() + (m_radius - 10) * qCos(textAngle * M_PI / 180);
        int lineStartY = m_center.y() - (m_radius - 10) * qSin(textAngle * M_PI / 180);
        int lineEndX = 0;
        int lineEndY = 0;
        if(textX < lineStartX)   //文本在左边
        {
            //可自行根据实际进行位置偏移的修改
            lineEndX = textX + textWidth/2;
            if(textY < lineStartY)   //文本在上边
            {
                lineEndY = textY + textHeight + 5;
            }
            else
            {
                lineEndY = textY - textHeight - 5;
            }
        }
        else
        {
            lineEndX = textX + textWidth/2;
            if(textY < lineStartY)
            {
                lineEndY = textY;
            }
            else
            {
                lineEndY = textY - textHeight - 5;
            }
        }
        painter.drawLine(lineStartX,lineStartY,lineEndX,lineEndY);

        //绘制终点
        painter.setPen(QPen(m_vData[i].color,5));
        painter.drawPoint(lineEndX,lineEndY);

        //将终点设为空心圆
        //painter.drawEllipse(QPoint(lineEndX,lineEndY),5,5);
        //painter.setPen(QPen(QColor("#FFFFFF"),1));
        //painter.setBrush(QColor("#FFFFFF"));
        //painter.drawEllipse(QPoint(lineEndX,lineEndY),3,3);
    }

    //绘制内圆,将饼图变为圆环
    painter.setPen(QPen(QColor("#FFFFFF"),10));
    painter.setBrush(QColor("#FFFFFF"));
    painter.drawEllipse(m_center,m_innerWidth,m_innerWidth);
}

三、设置圆环图数据

首先定义一个QVector容器来保存圆环图各区域数据的名称,颜色及数量,这里使用了findChild并结合输入框的控件名来找到对应QLineEdit,获取输入值存入容器,设置好饼图数据后使用update()进行绘图事件的更新

//更新饼图
void Widget::refreshChart()
{
    //设置圆环图各区域数据名称及颜色
    QVector<PieData> vData;
    QColor m_colors[5] = {QColor("#0286FF"),QColor("#FFA784"),QColor("#7EBBFF"),QColor("#DFEEFF"),QColor("#85D9FD")};
    QLineEdit *leNum;
    for(int i=0;i<5;i++)
    {
        leNum = ui->wg_test->findChild<QLineEdit *>("le_num_" + QString::number(i+1));   //找到对应lineedit
        if(leNum != 0)
        {
            int num = leNum->text().toInt();
            if(num > 0)   //过滤输入为空或0的数据
            {
                PieData data = PieData(QString("数据%1\n(%2)").arg(i+1).arg(num),num,m_colors[i]);
                vData.push_back(data);
            }
        }
    }
    this->setPieData(vData);
}

//设置饼图数据
void Widget::setPieData(QVector<PieData> vData)
{
    //获取数据
    m_vData = vData;

    //获取总数
    m_totality = 0;
    for(int i=0;i<m_vData.size();i++)
    {
        m_totality += m_vData[i].num;
    }
    this->update();
}

四、示例完整代码

1.widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QtMath>
#include <QPainter>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

struct PieData
{
    QString name;   //名称
    int num;        //数量
    QColor color;   //颜色

    PieData(QString name,int num,QColor color)
    {
        this->name = name;
        this->num = num;
        this->color = color;
    }
};

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void initWidget();

    //圆环图各参数函数接口
    void setRadius(int radius);
    void setInnerWidth(int width);
    void setCenter(QPoint center);
    void setStartAngle(qreal startAngle);
    void setTextDistance(int textDistance);
    void setPieData(QVector<PieData> vData);

    void refreshChart();

protected:
    void paintEvent(QPaintEvent *);

private slots:
    void on_pb_test_clicked();

private:
    Ui::Widget *ui;

    int m_radius;         //外圆半径
    int m_innerWidth;     //圆环内径
    QPoint m_center;      //圆心坐标
    qreal m_startAngle;   //圆环绘制起点
    int m_textDistance;   //文本与圆心的距离
    qreal m_totality;     //总数
    QVector<PieData> m_vData;   //数据容器
};
#endif // WIDGET_H

2.widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->initWidget();
}

Widget::~Widget()
{
    delete ui;
}

//初始化界面
void Widget::initWidget()
{
    //初始化变量
    m_radius = 0;
    m_innerWidth = 0;
    m_center = QPoint(0,0);
    m_startAngle = 0;
    m_textDistance = 0;
    m_totality = 0;
    m_vData.clear();
}

//设置外圆半径
void Widget::setRadius(int radius)
{
    m_radius = radius;
}

//设置圆环内径
void Widget::setInnerWidth(int width)
{
    m_innerWidth = width;
}

//设置圆心
void Widget::setCenter(QPoint center)
{
    m_center = center;
}

//设置圆环绘制起点
void Widget::setStartAngle(qreal startAngle)
{
    m_startAngle = startAngle;
}

//设置文本与圆心的距离
void Widget::setTextDistance(int textDistance)
{
    m_textDistance = textDistance;
}

//设置饼图数据
void Widget::setPieData(QVector<PieData> vData)
{
    //获取数据
    m_vData = vData;

    //获取总数
    m_totality = 0;
    for(int i=0;i<m_vData.size();i++)
    {
        m_totality += m_vData[i].num;
    }
    this->update();
}

//更新饼图
void Widget::refreshChart()
{
    //设置圆环图各区域数据名称及颜色
    QVector<PieData> vData;
    QColor m_colors[5] = {QColor("#0286FF"),QColor("#FFA784"),QColor("#7EBBFF"),QColor("#DFEEFF"),QColor("#85D9FD")};
    QLineEdit *leNum;
    for(int i=0;i<5;i++)
    {
        leNum = ui->wg_test->findChild<QLineEdit *>("le_num_" + QString::number(i+1));   //找到对应lineedit
        if(leNum != 0)
        {
            int num = leNum->text().toInt();
            if(num > 0)   //过滤输入为空或0的数据
            {
                PieData data = PieData(QString("数据%1\n(%2)").arg(i+1).arg(num),num,m_colors[i]);
                vData.push_back(data);
            }
        }
    }
    this->setPieData(vData);
}

//重写绘图事件
void Widget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing,true);   //抗锯齿

    //绘制圆角背景
    painter.setBrush(Qt::white);
    painter.setPen(Qt::NoPen);   //去除背景边框
    painter.drawRoundedRect(10,10,360,360,8,8);

    //绘制饼图
    qreal startAngle = m_startAngle;   //绘制起点
    qreal spanAngle = 0;   //各区域占比,覆盖角度
    for(int i=0;i<m_vData.size();i++)
    {
        painter.setPen(m_vData[i].color);
        painter.setBrush(m_vData[i].color);
        if(m_totality)   //防止总数为0
        {
            spanAngle = m_vData[i].num * 360 / m_totality;
        }
        painter.drawPie(m_center.x() - m_radius,m_center.y() - m_radius,m_radius * 2,m_radius * 2,startAngle * 16,spanAngle * 16);
        startAngle += spanAngle;
    }

    //绘制区域名称和占比
    startAngle = m_startAngle;
    spanAngle = 0;
    for(int i=0;i<m_vData.size();i++)
    {
        painter.setPen(QColor("#333333"));
        painter.setFont(QFont("STSongti-SC-Bold, STSongti-SC",16));
        if(m_totality)
        {
            spanAngle = m_vData[i].num * 360 / m_totality;
        }
        int textAngle = startAngle + spanAngle / 2;
        QString text = QString("%1").arg(m_vData[i].name);
        int textWidth = painter.fontMetrics().horizontalAdvance(text);
        int textHeight = painter.fontMetrics().height();
        int textX = m_center.x() + m_textDistance * qCos(textAngle * M_PI / 180) - textWidth / 2;
        int textY = m_center.y() - m_textDistance * qSin(textAngle * M_PI / 180) + textHeight / 2;
        startAngle += spanAngle;

        //绘制文本
        QRect rect(textX,textY - textHeight,textWidth + 10,textHeight * 2);
        painter.drawText(rect,Qt::AlignCenter,text);

        //绘制连接线,文本要靠近对应区域,需要修改连接线终点位置
        painter.setPen(m_vData[i].color);
        int lineStartX = m_center.x() + (m_radius - 10) * qCos(textAngle * M_PI / 180);
        int lineStartY = m_center.y() - (m_radius - 10) * qSin(textAngle * M_PI / 180);
        int lineEndX = 0;
        int lineEndY = 0;
        if(textX < lineStartX)   //文本在左边
        {
            //可自行根据实际进行位置偏移的修改
            lineEndX = textX + textWidth/2;
            if(textY < lineStartY)   //文本在上边
            {
                lineEndY = textY + textHeight + 5;
            }
            else
            {
                lineEndY = textY - textHeight - 5;
            }
        }
        else
        {
            lineEndX = textX + textWidth/2;
            if(textY < lineStartY)
            {
                lineEndY = textY;
            }
            else
            {
                lineEndY = textY - textHeight - 5;
            }
        }
        painter.drawLine(lineStartX,lineStartY,lineEndX,lineEndY);

        //绘制终点
        painter.setPen(QPen(m_vData[i].color,5));
        painter.drawPoint(lineEndX,lineEndY);

        //将终点设为空心圆
        //painter.drawEllipse(QPoint(lineEndX,lineEndY),5,5);
        //painter.setPen(QPen(QColor("#FFFFFF"),1));
        //painter.setBrush(QColor("#FFFFFF"));
        //painter.drawEllipse(QPoint(lineEndX,lineEndY),3,3);
    }

    //绘制内圆,将饼图变为圆环
    painter.setPen(QPen(QColor("#FFFFFF"),10));
    painter.setBrush(QColor("#FFFFFF"));
    painter.drawEllipse(m_center,m_innerWidth,m_innerWidth);
}

//测试
void Widget::on_pb_test_clicked()
{
    //设置圆环各参数
    this->setRadius(100);
    this->setInnerWidth(70);   //设为0即为饼图
    this->setCenter(QPoint(180,180));
    this->setStartAngle(90);   //区域绘制方向为逆时针
    this->setTextDistance(150);
    this->refreshChart();
}

3.widget.ui
请添加图片描述

五、下载链接

我的示例百度网盘链接:https://pan.baidu.com/s/1q4S87YnMxhUd3w1l0Yr4tA
提取码:xxcj


总结

这个示例详细的讲述了使用QPinter进行圆环图的绘制,其中难点在于各区域文本与区域边界中间位置的连接,这里也是使用到了基础的数学三角函数来求取相关值,示例中的文本位置还是要根据实际进行偏移量的修改。另外可以看到其中的函数接口,这里你有没有想到什么呢?其实将其修改为自定义控件,当作一个组件来使用,后续使用只需要在ui界面上或者自己添加的widget对象提升为该控件,就可以很方便的实现圆环图的绘制,至于如何修改,这里就不进行介绍了,实现自定义控件的方式可以看看我之前写的文章:(一)Qt实现自定义控件的两种方式—提升法


hello:
共同学习,共同进步,如果还有相关问题,可在评论区留言进行讨论。

参考博客:QPainter绘制饼图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/31359.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】动态规划 刷题训练(二)

文章目录 62. 不同路径题目解析状态转移方程完整代码 63. 不同路径 II题目解析状态转移方程完整代码 剑指 Offer 47. 礼物的最大价值题目解析状态转移方程完整代码 62. 不同路径 点击查看&#xff1a;不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图…

数据库架构是否该随着公司估值一起变化?

原文&#xff5c;The growing pains of database architecture 作者&#xff5c;Tim Liang, Software Engineer at Figma 2020 年&#xff0c;因为 Figma 不断加入新功能&#xff0c;筹备第二条产品线和用户不断增长导致数据库流量每年以 3x 速度增长&#xff0c;我们的基础设…

云原生之深入解析Kubernetes中Kubectl Top如何进行资源监控

一、Kubectl top 的使用 kubectl top 是基础命令&#xff0c;但是需要部署配套的组件才能获取到监控值&#xff1a; 1.8 以下&#xff1a;部署 heapter&#xff1b; 1.8 以上&#xff1a;部署 metric-server&#xff1b; kubectl top node&#xff1a;查看 node 的使用情况&a…

【C++】构造函数调用规则

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01;时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 1、缘起 &#xff08;1&#xff09;默认情况下&#xff0c;C 编译器至少给一个类添加 3 个函数 ① 默认构造函数&#xff08;无参&#…

开源软件介绍——国内和国际主要开源社区

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来看一看国内和国际上有哪些主要开源社区。 开源社区的定义 开源社区又称为开放源代码社区&#xff0c;一般由拥有共同兴趣爱好的人组成。根据相应的开源软件许可证协议公布软件源代码的网络平台&a…

ChatGPT从入门到精通,深入认识ChatGPT

ChatGPT从入门到精通&#xff0c;一站式掌握办公自动化/爬虫/数据分析和可视化图表制作 全面AI时代就在转角 道路已经铺好了 “局外人”or“先行者” 就在此刻 等你决定1、ChatGPT从入门到精通&#xff0c;一站式掌握办公自动化/爬虫/数据分析和可视( 点击观看完整版本 )https…

Clickhouse之物化视图分享

前言 ClickHouse广泛用于用户和系统日志查询场景中&#xff0c;主要针对于OLAP场景&#xff0c;为业务方提供稳定高效的查询服务。在业务场景下&#xff0c;数据以不同的格式、途径写入到clickhouse。用传统JOIN方式查询海量数据&#xff0c;通常有如下痛点: 每个查询的代码冗…

CTFshow-pwn入门-前置基础pwn23-pwn25

pwn23-25的题目会涉及到ret2shellcode、ret2libc等内容&#xff0c;本篇文章只会侧重研究这几道题目的wp&#xff0c;不会过多涉及到ret2shellcode、ret2libc的基本原理&#xff0c;等有时间再来写关于ret2libc、ret2shellcode…的相关内容。大家可以参考CTFwiki的文章去慢慢学…

【机器学习】十大算法之一 “SVM”

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,python领域博主爱笑的男孩。擅长深度学习,活动,python,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个…

Kubernetes(k8s)部署模式发展

目录 1 简介2 物理单机(~2000)2.1 主要代表 3 虚拟化&#xff1a;初期&#xff08;2001~2009&#xff09;3.1 VMware3.2 laaS 4 虚拟化&#xff1a;成熟期&#xff08;2010~至今&#xff09;4.1 OpenStack4.2 虚拟化四巨头 5 容器化:&#xff08;2013-至今&#xff09;5.1 Dock…

【备战秋招】每日一题:2023.04.26-华为OD机式-第三题-MC方块

在线评测链接:P1231 题目内容 MC最新版本更新了一种特殊的方块&#xff0c;幽匿催发体。这种方块能够吸收生物死亡掉落的经验并感染周围方块&#xff0c;使其变成幽匿块。Steve想要以此为基础尝试搭建一个经验仓库&#xff0c;他来到了创造超平坦模式&#xff0c;在只有草方块…

被测系统架构与数据流分析

开源项目litemall系统架构(https://github.com/linlinjava/litemall) 角色与数据用户产品前端技术栈后端技术栈数据存储 开源项目Mall的系统架构(https://github.com/macrozheng/mall) 角色与数据用户产品前端技术栈后端技术栈服务治理技术栈监控技术栈大数据处理技术栈数据存…

自动化测试工具 AirTest 的使用方法与简介

目录 前言&#xff1a; Airtest简介 1.基于图像识别的Airtest框架 2.基于UI识别的Poco框架 Airtest环境搭建 Airtest布局 Airtest使用步骤 第一步&#xff1a;连接移动设备 第二步&#xff1a;创建一个.air文件&#xff08;也就是我们的测试脚本&#xff09; 第三步&#xff1a…

【MySQL数据库 | 第二十篇】explain执行计划

目录 前言&#xff1a; explain&#xff1a; 语法&#xff1a; 总结&#xff1a; 前言&#xff1a; 上一篇我们介绍了从时间角度分析MySQL语句执行效率的三大工具&#xff1a;SQL执行频率&#xff0c;慢日志查询&#xff0c;profile。但是这三个方法也只是在时间角度粗略的…

如何在 XMind 中绘制流程图

XMind 是专业强大的思维导图软件,由于其结构没有任何限制,很多朋友特别喜欢用它来绘制流程图。禁不住大家的多次询问,今天 XMind 酱就将这简单的流程图绘图方法分享给大家。 在 XMind 中,绘制流程图的主角是「自由主题」和「联系」。它们可以打破思维导图的限制,让你自由…

Type-C PD显示器方案简介

方案概述 LDR6020 Type-C PD显示器方案可以给显示器提供一个全功能C口&#xff0c;支持手机&#xff0c;电脑&#xff0c;游戏主机等一线投屏功能&#xff0c;同时支持PD快充输出。LDR6020内置了 USB Power Delivery 控制器和 PD BMC PHY 收发器&#xff0c;支持PD2.0/3.0等快…

Java多线程与并发

1、JDK版本的选择 选择JDK8、JDK11进行讲解的原因&#xff1a;Oracle长期支持 2、进程和线程的区别 进程和线程的由来 3、进程与线程的区别 进程是资源分配的最小单位,线程是cpu调度的最小单位. 所有与进程相关的资源&#xff0c;都被记录在PCB(进程控制块)中。进程是抢占…

数学建模竞赛国赛入场券之攻略

数学建模竞赛国赛入场券之攻略 1.团队契合度 在3天的准备时间中&#xff0c;如果是临时组建的草台班子光处理分歧可能就已经耗掉一半时间&#xff0c;最好在赛前就完成磨合&#xff0c;像一起做模拟题练练手之类&#xff0c;甲准备图论、乙准备优化方法&#xff0c;然后再一块…

存储笔记8 ipsan

Module Objectives IP SAN的组件 IP SAN的好处 描述SAN中的IP融合及其影响 描述的基本架构 –iSCSI –FCIP –FCoE 讨论IP SAN技术的市场驱动因素 列出IP SAN技术 列出iSCSI的组件和连接选项 描述iSCSI体系结构和拓扑结构 解释iSNS操作 描述FCIP的体系结构 IP SAN互联…

Redis持久化机制与Redis事务

一、Redis 持久化机制 Redis 是个基于内存的数据库。那服务一旦宕机&#xff0c;内存中数据必将全部丢失。所以丢失数据的恢复对于 Redis 是十分重要的&#xff0c;我们首先想到是可以从数据库中恢复&#xff0c;但是在由 Redis 宕机时&#xff08;说明相关工作正在运行&#…