Qt에서 QPainter를 사용하여 인터페이스에서 파이 차트 및 도넛 차트 그리기 실현


머리말

이전 기사에서는 파이 차트를 그리기 위해 Qt에서 Charts 모듈을 사용하는 방법에 대해 설명했습니다. QChart는 ui 인터페이스의 지정된 위치에서 파이 차트 및 링 차트 그리기를 구현 하지만 사용 중에 원하는 효과를 잘 얻을 수 없으며 그리기 이벤트 Qt에서 이 문제를 해결할 수 있으므로 QPainter 및 QPaintEvent를 사용하여 원형 차트 및 도넛 차트의 사용자 지정 그리기를 실현하기로 결정했습니다. 여기에 간단한 예제를 작성하고 모든 사람이 배울 수 있도록 관련 코드를 보여주었습니다. 실수가 있으면 모두 비판과 수정을 환영합니다.

프로젝트 효과
사진 설명을 추가해주세요


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

1. 예시 설명

먼저 관련 헤더 파일을 추가합니다.

#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;   //数据容器

함수 인터페이스 예:
사진 설명을 추가해주세요

2. 도넛 차트를 그리는 단계

1. drawRoundedRect를 사용하여 파이 차트의 둥근 배경을 그립니다
.
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);
}

3. 도넛 차트 데이터 설정

먼저 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();
}

4. 전체 코드 예

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
사진 설명을 추가해주세요

5. 다운로드 링크

내 예제 Baidu 네트워크 디스크 링크: https://pan.baidu.com/s/1q4S87YnMxhUd3w1l0Yr4tA
추출 코드: xxcj


요약하다

이 예제는 QPinter를 사용하여 링 차트를 그리는 방법을 자세히 설명합니다.어려움은 각 영역의 텍스트와 영역 경계의 중간 위치 사이의 연결에 있습니다.여기서 기본 수학 삼각 함수를 사용하여 해당 값을 얻습니다. 예제의 텍스트 위치는 여전히 실제 상황에 따라 오프셋을 수정해야 합니다. 또한 기능 인터페이스를 볼 수 있습니다.여기에서 생각한 것이 있습니까? 사실 커스텀 컨트롤로 수정해서 컴포넌트로 사용하고 나중에 사용할 때는 UI 인터페이스의 위젯 개체를 업그레이드하거나 직접 이 컨트롤로 업그레이드하면 도넛 차트 그리기를 쉽게 구현할 수 있습니다. 수정하는 방법에 대해서는 여기서는 소개하지 않겠습니다.커스텀 컨트롤을 구현하는 방법은 이전에 쓴 글에서 볼 수 있습니다: (1) Qt가 커스텀 컨트롤을 구현하는 두 가지 방법 - 승격 방법


안녕하세요:
함께 배우고 함께 발전해 나가세요.관련된 질문이 있으면 댓글 영역에 메시지를 남겨 토론할 수 있습니다.

참조 블로그: QPainter는 원형 차트를 그립니다.

Supongo que te gusta

Origin blog.csdn.net/XCJandLL/article/details/131089682
Recomendado
Clasificación