Qt实现自定义饼状统计图

4 篇文章 2 订阅

       由于随着需求的多样化,个性化,Qt自带的原生控件QChart已经不能够满足我们的追求个性与美完美融合的过分要求。所以在闲暇时间把玩C艹, 自定义绘制了饼状统计图。该自定义控件实现了颜色自定义设置,鼠标悬浮突出显示该部分在全部数据的占比。话不多说,直接上效果图:   

bandicam

 一、绘制背景(即每个扇形区域);

void CustomPieChart::drawValues(QPainter *painter)
{
    auto iter = m_valueMap.begin();
    QString key = "";
    qreal value = 0.0;
    qreal startAngle = 0.0;
    qreal angleLength = 0.0;
    for(;iter!=m_valueMap.end();iter++){
        key = iter.key();
        value = iter.value();
        angleLength = (value / m_total) * 360 - BLANK_W;
        gradientArc(painter, key, startAngle, angleLength, m_colorMap[key]);
        m_anglePairMap.insert(key, QPair<qreal, qreal>(startAngle, angleLength));
        startAngle += (angleLength + BLANK_W);
    }
}


void CustomPieChart::gradientArc(QPainter *painter, QString key, qreal startAngle, qreal angleLength, QColor color)
{
    qreal radius = m_valueStateMap.value(key)?m_radius+SUPERPLUS_H:m_radius;
    painter->setBrush(color);
    QRectF rectf(-radius, -radius, radius * 2, radius * 2);
    QPainterPath path;
    path.arcTo(rectf, startAngle, angleLength);
    painter->drawPath(path);
}

绘制完成后的效果图如下所示:

 

 二、绘制中间的圆形区域:

void CustomPieChart::drawMidRound(QPainter *painter)
{
    if(m_total == 0)
        return;
    QLinearGradient linear(QPointF(0, 0), QPointF(150, 150));
    linear.setColorAt(0, QColor("#c5c5c5"));
    linear.setColorAt(1, QColor("#ffffff"));
    painter->setBrush(linear);
    painter->drawEllipse(QPointF(0, 0), m_midR, m_midR);
}

绘制完成后的效果图如下所示:

 三、绘制中心圆形区域

void CustomPieChart::drawCenterRound(QPainter *painter)
{
    if(m_total==0)
        return;
    painter->setBrush(Qt::white);
    painter->drawEllipse(QPointF(0, 0), m_centerR, m_centerR);
}

绘制完成后的效果图如下所示:

 四、绘制中心文字

void CustomPieChart::drawText(QPainter *painter)
{
    if(m_total==0)
        return;
    QPen pen;
    pen.setColor(QColor("#26303c"));
    pen.setWidth(3);
    painter->setPen(pen);
    painter->setFont(QFont("Source Han Sans CN", m_fontSize));
    int value = qCeil((float)m_valueMap.value(m_curKey) / (float)m_total * 100.0);
    qreal w = QFontMetrics(painter->font()).width(QObject::tr("%1%").arg(value));
    painter->drawText(-(w/2), 10, QObject::tr("%1%").arg(value));

    painter->setFont(QFont("Source Han Sans CN", m_fontSize - 16));
    w = QFontMetrics(painter->font()).width(m_curKey);
    painter->drawText(-(w/2), 40, m_curKey);
}

绘制完成后的效果图如下所示:

         到目前为止,整个饼状图已经绘制完成,现在需要实现的就是鼠标悬浮每个区域后突出显示该部分,并且中心文字为该部分的占比以及名称。这样就需要重新实现QWidget的鼠标移动事件,当我们鼠标在移动的时候,指向某一块后,将该快的半径大于其他区域的半径即就可以实现突出显示的效果,mouseMoveEvent,默认下只接收鼠标按下后的移动事件,所以必须设置:

setMouseTracking(true);

重点:如何找到鼠标当前所在的区域:以圆心为坐标原点,将鼠标所在的坐标为pos(x, y)转换为原点坐标;鼠标到圆心的距离 L等于:

qSqrt(qPow(pos.x(), 2) + qPow(pos.y(), 2));

如果当前L>midR && L< R, 那么知道当前鼠标在彩色的块区域部分,在绘制扇形区域时候会记录当前区域的其实角度和结束角度,那么计算pos和原点的夹角(不同象限不同计算);判断当前夹角在哪一块区域内,则突出显示该部分。

//起点到鼠标跨过的角度
float CustomPieChart::getPosInAngle(QPointF pos)
{
    qreal a = qAbs(pos.y());
    qreal c = qSqrt(qPow(pos.x(), 2) + qPow(pos.y(), 2));
    qreal radians = qAsin((float)a/(float)c);
    qreal rangle = 180.0 / M_PI * radians;
    if(pos.x()>0 && pos.y()>0)
        return rangle;
    if(pos.x()<0 && pos.y()>0)
        return 180.0 - rangle;
    if(pos.x()<0 && pos.y()<0)
        return 180.0 + rangle;
    if(pos.x()>0 && pos.y()<0)
        return 360.0 - rangle;
    else
        return 0.0;
}

主要代码头文件:

#ifndef CUSTOM_PIE_CHART_H
#define CUSTOM_PIE_CHART_H

#include <QWidget>
#include <QObject>
#include <QPainter>
#include <QPaintEvent>
#include <QMouseEvent>
#include <QEvent>

class CustomPieChart : public QWidget
{
    QOBJECT_H
public:
    explicit CustomPieChart(QWidget* parent = nullptr);
    explicit CustomPieChart(const QSize& size, QWidget* parent = nullptr);
    ~CustomPieChart();
    void setThisFixedSize(const QSize& size);
    QSize thisSize()const;

    void setData(const QMap<QString, int>& dataMap);
    QMap<QString, int> data()const;

    void setValueColor(const QMap<QString, QColor>& colorMap);
    QMap<QString, QColor> valuesColor()const;

protected:
    void paintEvent(QPaintEvent* e);
    void leaveEvent(QEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
private:
    void drawValues(QPainter* painter);
    void drawMidRound(QPainter* painter);
    void drawCenterRound(QPainter* painter);
    void drawText(QPainter* painter);
    void gradientArc(QPainter *painter, QString key, qreal startAngle, qreal angleLength, QColor color);
    float getPosInAngle(QPointF pos);

    QMap<QString, QColor> m_colorMap;
    QMap<QString, int> m_valueMap;

    qreal m_radius;
    qreal m_total = 0;

    qreal m_midR;
    qreal m_centerR;

    QMap<QString, QPair<qreal, qreal>> m_anglePairMap;
    QMap<QString, bool> m_valueStateMap;
    QRect m_midRect, m_centerRect;
    qreal m_fontSize;
    QString m_curKey = QString();
};
#endif // CUSTOM_PIE_CHART_H

源文件:

#include "custom_pie_chart.h"
#include <QDebug>
#include <QFontMetrics>
#include <qmath.h>

#define  SUPERPLUS_H       10   //悬浮状态超出高度
#define  BLANK_W           1    //各个value 间间隔
#define  DEFAULT_FONT_SIZE 40   //默认字体大小

CustomPieChart::CustomPieChart(QWidget* parent)
    :QWidget(parent)
{
    setMouseTracking(true);
    m_radius = width()/2 - SUPERPLUS_H;  //大圆的半径
    m_midR = m_radius*2/3;               //中间圆的半径
    m_centerR = m_radius/2;              //中心圆的半径
    m_fontSize = DEFAULT_FONT_SIZE;      //字体默认大小
}

CustomPieChart::CustomPieChart(const QSize &size, QWidget *parent)
    :QWidget(parent)
{
    setMouseTracking(true);
    setFixedSize(size);
    m_radius = width()/2 - SUPERPLUS_H;
    m_midR = m_radius*2/3;
    m_centerR = m_radius/2;
    m_fontSize = DEFAULT_FONT_SIZE;
}

CustomPieChart::~CustomPieChart(){}


void CustomPieChart::setThisFixedSize(const QSize &size)
{
    setFixedSize(size);
    m_radius = width()/2 - 20;
    m_midR = m_radius*2/3;
    m_centerR = m_radius/2;
    update();
}
QSize CustomPieChart::thisSize()const
{
    return size();
}

void CustomPieChart::setData(const QMap<QString, int>& dataMap)
{
    m_valueStateMap.clear();
    m_colorMap.clear();
    m_anglePairMap.clear();
    m_valueMap = dataMap;
    auto iter = m_valueMap.begin();
    for(int index = 1;iter!=m_valueMap.end();iter++){
        m_total += iter.value();
        if(index % 2 !=0)
            m_colorMap.insert(iter.key(), QColor("#ee5b5b"));
        else
            m_colorMap.insert(iter.key(), QColor("#30c1d8"));
        if(index == 1)
            m_curKey = iter.key();
        bool state = (index == 1)?true:false;
        m_valueStateMap.insert(iter.key(), state);
        index++;
    }
    if(m_colorMap.size()>0 && m_valueMap.size()>0)
        update();
}

QMap<QString, int> CustomPieChart::data()const
{
    return m_valueMap;
}

void CustomPieChart::setValueColor(const QMap<QString, QColor> &colorMap)
{
    m_colorMap = colorMap;
    if(m_colorMap.size()>0 && m_valueMap.size()>0)
        update();
}

QMap<QString, QColor> CustomPieChart::valuesColor()const
{
    return m_colorMap;
}

void CustomPieChart::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.translate(width() >> 1, height() >> 1);
    painter.setPen(Qt::NoPen);
    drawValues(&painter);      //绘制每一个扇形值区域
    drawMidRound(&painter);    //绘制中部圆
    drawCenterRound(&painter); //绘制中心圆
    drawText(&painter);        //绘制占比
}

void CustomPieChart::drawValues(QPainter *painter)
{
    auto iter = m_valueMap.begin();
    QString key = "";
    qreal value = 0.0;
    qreal startAngle = 0.0;
    qreal angleLength = 0.0;
    for(;iter!=m_valueMap.end();iter++){
        key = iter.key();
        value = iter.value();
        angleLength = (value / m_total) * 360 - BLANK_W;
        gradientArc(painter, key, startAngle, angleLength, m_colorMap[key]);
        m_anglePairMap.insert(key, QPair<qreal, qreal>(startAngle, angleLength));
        startAngle += (angleLength + BLANK_W);
    }
}

void CustomPieChart::drawMidRound(QPainter *painter)
{
    if(m_total == 0)
        return;
    QLinearGradient linear(QPointF(0, 0), QPointF(150, 150));
    linear.setColorAt(0, QColor("#c5c5c5"));
    linear.setColorAt(1, QColor("#ffffff"));
    painter->setBrush(linear);
    painter->drawEllipse(QPointF(0, 0), m_midR, m_midR);
}

void CustomPieChart::drawCenterRound(QPainter *painter)
{
    if(m_total==0)
        return;
    painter->setBrush(Qt::white);
    painter->drawEllipse(QPointF(0, 0), m_centerR, m_centerR);
}

void CustomPieChart::drawText(QPainter *painter)
{
    if(m_total==0)
        return;
    QPen pen;
    pen.setColor(QColor("#26303c"));
    pen.setWidth(3);
    painter->setPen(pen);
    painter->setFont(QFont("Source Han Sans CN", m_fontSize));
    int value = qCeil((float)m_valueMap.value(m_curKey) / (float)m_total * 100.0);
    qreal w = QFontMetrics(painter->font()).width(QObject::tr("%1%").arg(value));
    painter->drawText(-(w/2), 10, QObject::tr("%1%").arg(value));

    painter->setFont(QFont("Source Han Sans CN", m_fontSize - 16));
    w = QFontMetrics(painter->font()).width(m_curKey);
    painter->drawText(-(w/2), 40, m_curKey);
}

void CustomPieChart::gradientArc(QPainter *painter, QString key, qreal startAngle, qreal angleLength, QColor color)
{
    qreal radius = m_valueStateMap.value(key)?m_radius+SUPERPLUS_H:m_radius;
    painter->setBrush(color);
    QRectF rectf(-radius, -radius, radius * 2, radius * 2);
    QPainterPath path;
    path.arcTo(rectf, startAngle, angleLength);
    painter->drawPath(path);
}

void CustomPieChart::leaveEvent(QEvent *event)
{
    return QWidget::leaveEvent(event);
}

void CustomPieChart::mouseMoveEvent(QMouseEvent *event)
{
    QPointF pos = event->pos();
    pos = QPointF(pos.x() - m_radius, m_radius - pos.y());
    qreal l = qSqrt(qPow(pos.x(), 2)+qPow(pos.y(), 2));
    //内圆
    if(l < m_radius/2){
        m_centerR = m_radius/2 + SUPERPLUS_H;
        m_fontSize = DEFAULT_FONT_SIZE + 2;
        update();
        return QWidget::mouseMoveEvent(event);
    }
    //外圈圆
    if(l > m_radius*2/3 && l<m_radius){
        m_centerR = m_radius/2;
        m_fontSize = DEFAULT_FONT_SIZE;
        qreal rangle = getPosInAngle(pos);
        auto iter = m_anglePairMap.begin();
        for(;iter!=m_anglePairMap.end();iter++){
            QPair<qreal, qreal> value = iter.value();
            if(rangle>=value.first && rangle<=(value.first + value.second)){
                m_valueStateMap[iter.key()] = true;
                m_curKey = iter.key();
            }
            else
                m_valueStateMap[iter.key()] = false;
        }
        update();
        return QWidget::mouseMoveEvent(event);
    }
    return QWidget::mouseMoveEvent(event);
}

float CustomPieChart::getPosInAngle(QPointF pos)
{
    qreal a = qAbs(pos.y());
    qreal c = qSqrt(qPow(pos.x(), 2) + qPow(pos.y(), 2));
    qreal radians = qAsin((float)a/(float)c);
    qreal rangle = 180.0 / M_PI * radians;
    if(pos.x()>0 && pos.y()>0)
        return rangle;
    if(pos.x()<0 && pos.y()>0)
        return 180.0 - rangle;
    if(pos.x()<0 && pos.y()<0)
        return 180.0 + rangle;
    if(pos.x()>0 && pos.y()<0)
        return 360.0 - rangle;
    else
        return 0.0;
}

测试代码:

    ui->setupUi(this);
    setMouseTracking(true);
    m_pieChartView = new CustomPieChart(QSize(400, 400), this);
    QGridLayout* glayout = new QGridLayout(this);
    glayout->addWidget(m_pieChartView);

    QMap<QString, int> dataMap;
    dataMap.insert("a", 24);
    dataMap.insert("b", 40);
    dataMap.insert("c", 70);
    dataMap.insert("d", 30);
    dataMap.insert("e", 50);
    dataMap.insert("f", 90);
    dataMap.insert("g", 35);
    dataMap.insert("h", 35);

    QMap<QString, QColor> colorMap;
    colorMap.insert("a", QColor("#4EEE94"));
    colorMap.insert("b", QColor("#6495ED"));
    colorMap.insert("c", QColor("#8470FF"));
    colorMap.insert("d", QColor("#FFD700"));
    colorMap.insert("e", QColor("#EE6363"));
    colorMap.insert("f", QColor("#FF1493"));
    colorMap.insert("g", QColor("#EE2C2C"));
    colorMap.insert("h", QColor("#A020F0"));

    m_pieChartView->setData(dataMap);
    m_pieChartView->setValueColor(colorMap);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值