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

news2025/1/16 1:52:18

文章目录

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


前言

前面的文章有讲述使用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.coloradmin.cn/o/678914.html

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

相关文章

Embedding 嵌入知识入门

原文首发于博客文章Embedding 嵌入知识入门 文本嵌入是什么 向量是一个有方向和长度的量&#xff0c;可以用数学中的坐标来表示。例如&#xff0c;可以用二维坐标系中的向量表示一个平面上的点&#xff0c;也可以用三维坐标系中的向量表示一个空间中的点。在机器学习中&#x…

强化学习从基础到进阶-案例与实践[4.1]:深度Q网络-DQN项目实战CartPole-v0

【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧&#xff08;调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍&#xff1a;【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧…

Redis 原理

Redis 原理 动态字符串SDS Redis中保存的key时字符串&#xff0c;value往往是字符串或字符串集合&#xff0c;字符串是Redis中常见的数据结构 Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存在很多问题&#xff0c;使用起来不方便 Redis构建了一种新型的字…

Web网页制作-知识点(3)——HTML5新增标签、CSS简介、CSS的引入方式、选择器、字体属性、背景属性、表格属性、关系选择器

目录 HTML5新增标签 CSS简介 CSS概念 CSS的作用 语法 CSS的引入方式 内联样式&#xff08;行内样式&#xff09; 内部样式 外部样式&#xff08;推荐&#xff09; 选择器 全局选择器 元素选择器 类选择器 ID选择器 合并选择器 选择器的优先级 字体属性 …

Linux——文件基础IO的文件描述符和重定向实现理解

目录 前言&#xff1a; 首先来回顾一下open函数&#xff0c;即在进程中同时打开多个文件&#xff1a; Linux底层进程与文件的关系 &#xff1a; 二.重定向的实现 什么是重定向&#xff1f; 方法1&#xff1a; 2.1关闭stdin&#xff1a; 运行结果&#xff1a; ​编辑由结果知…

统计字符串数组中各元素中指定字符串出现的次数numpy.char.count()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 统计字符串数组中各元素中 指定字符串出现的次数 numpy.char.count() [太阳]选择题 下列代码最后输出的结果是&#xff1f; import numpy as np s np.array([I, Love, Python]) print("…

ChatGPT底层架构Transformer技术及源码实现(二)

ChatGPT底层架构Transformer技术及源码实现(二) Gavin大咖微信:NLP_Matrix_Space 3.2 图解Transformer精髓之架构设计、数据训练时候全生命周期、数据在推理中的全生命周期、矩阵运算、多头注意力机制可视化等 如图3-14所示,是Transformer编解码的示意图,中间有个关键内…

LFS11.3在VMware中安装后需要做的准备

参考lfs 11.3和Blfs 11.3 先简单罗列一下要做的步骤&#xff0c;后续有机会再补充一下细节&#xff0c;遇到问题欢迎读者留言。 1、配置vmware中的网络连接 使用vmware net8 net模式&#xff0c;选用VMnet 配置网络连接/etc/sysconfig/ 目录下ifconfig.*** &#xff08;***为…

fanuc机器人安装profinet IO基板产生报警

fanuc机器人安装profinet IO基板产生报警: SYST-302 请关闭电源 PRIO-397 PMIO 固件需要更新 %x %x 问题描述:新的R30iB‐Plus柜的GSDML 文件与R30iB柜的GSDML文件是不同的,GSDML文件与R834固件版本不匹配的话,会无法扫描到R834的卡,导致无法通讯 解决方法:确认 Expecte…

Diffusion Models: 方法和应用的综合调查 【01】Diffusion Models基础

Diffusion Models: 方法和应用的综合调查 【01】Diffusion Models基础 原文链接&#xff1a;Diffusion Models: 方法和应用的综合调查 【01】Diffusion Models基础 GitHub: https://github.com/YangLing0818/Diffusion-Models-Papers-Survey-Taxonomy. Paper&#xff1a; https…

MySQL学习基础篇(一)

一、数据库概述 1. 为什么要使用数据库 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;特别是企业级应用&#xff0c;数据持久化意味着将内存中的数据保存到硬盘上加以”固化”&#xff0c;而持久化的实现过程大多通…

程序员编程效率的大敌:中断与上下文切换

程序员编程效率的大敌&#xff1a;中断与上下文切换 首先解释一下中断和上下文切换&#xff1a; 中断: 编程时被打断, 比如被聊天软件/电子邮件/电话/当面打断等&#xff1b;上下文切换&#xff1a;即任务的切换&#xff0c;有自己主动切换&#xff0c;有伴随中断的新任务&am…

C# 静态构造函数学习

静态构造函数用于初始化类中的静态数据或执行仅需一次的特定操作&#xff0c;静态构造函数将在创建第一个实例或引用类中的静态成员之前自动调用。 静态构造函数具有以下特点&#xff1a; 静态构造函数不使用访问权限修饰符修饰或不具有参数&#xff1b; 类或结构体中…

Proxmox VE 8 发布 - 开源虚拟化管理平台

Proxmox VE 8 发布 - 开源虚拟化管理平台 请访问原文链接&#xff1a;https://sysin.org/blog/proxmox-ve-8/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 宣布 Proxmox 虚拟环境的主要版本 8.0&#xff01;它基于出色的 De…

SkyWalking--用代码手动获取traceId的方法

原文网址&#xff1a;SkyWalking--用代码手动获取traceId的方法_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java项目如何用代码手动获取SkyWalking的traceId。 引入依赖 <dependency><groupId>org.apache.skywalking</groupId><artifactId>apm-tool…

【PCB专题】如何使用Assign color在 Allegro 中快速区别不同网络?

在PCB Layout中经常要查看网络走线,比如电源路径是否合理,线宽是否合适,网络是否形成环路等等。一般我们使用的是高亮网络来查看。 困扰 如果是单一网络这样做是没有什么问题的,但如果是多条网络,就一种颜色会很难看清。就算不同的网络是不同的条纹,在布线比较密集的时…

JavaScript 手写代码 第三期

文章目录 1. 为什么要手写代码&#xff1f;2. 手写代码2.1 函数柯里化2.1.1 基本使用2.1.2 手写实现 2.2 sleep函数2.2.1 简单使用2.2.2 手写实现 2.3 Object.assign() 方法2.3.1 基本使用2.3.2 具体示例2.3.3 具体思路2.3.4 具体实现 1. 为什么要手写代码&#xff1f; 我们在…

ChatGPT底层架构Transformer技术及源码实现(三)

ChatGPT底层架构Transformer技术及源码实现(三) 贝叶斯Bayesian Transformer数学推导论证过程全生命周期详解及底层神经网络物理机制剖析 Gavin大咖微信:NLP_Matrix_Space 从数学的角度来讲,线性转换 其中函数g联合了所有头的操作结果,每个头的产生是采用一个f_att的…

RedHat红帽认证---RHCE

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; RHCE 1.安装和配置 Ansible 安装和配置 Ansible按照下方所述&#xff0c;在控制节点 control 上安装和配置 Ansible&#xff1a;安装所需的软件包创建名为 /home/gre…