QPainter绘制3D 饼状图

发布于:2025-02-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

先展示图片 

核心代码如下:

pie.h

#ifndef Q3DPIE_H
#define Q3DPIE_H

#include <QtGui/QPen>
#include <QtGui/QBrush>


class  Pie
{
public:

    double value;           
    QBrush brush;           
    QString description;    

    double percentValue;
    QString percentStr;
    double startAngle;
    double spanAngle;
    QPointF startPoint;
    QPointF endPoint;
    QPointF centerPoint;

    bool isExploded;

    QRectF boundRect;
    QRectF boundShadowRect;

private:

};

class PieSide
{
public:
    Pie *pie;
    double angle;
};

#endif // Q3DPIE_H

q3dpiechart.h

#ifndef Q3DPIECHART_H
#define Q3DPIECHART_H


#include <QWidget>
#include <QPainter>
#include <QToolTip>
#include <QMouseEvent>

#include "q3dpiechart_global.h"
#include "pie.h"
#include<QPair>


class  q3dpiechart : public QWidget
{
public:
	q3dpiechart(QWidget* parent = NULL);
	~q3dpiechart();

public:
    void addPie(double v, const QString &desc);
    void addPie(double v, const QString &desc, const QColor &fillColor);
    void removePie(int pieIndex);
    void clear();


protected:
    void paintEvent(QPaintEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void resizeEvent(QResizeEvent *event);
    void timerEvent(QTimerEvent * event);


private:
    void refreshChart();

    void drawBackground(QPainter &painter);
    void drawBackground3DShadow(QPainter &painter, const QRectF &rect);
    void draw3DPieSuface(QPainter &painter, const Pie &pie, const QRectF &rcBound, const QBrush &brush, bool isTop = true);
    void draw3DPieLabel(QPainter &painter, const Pie &pie);
    void draw3DPieLabelOut(QPainter &painter, const Pie &pie, QRectF labelSizeRect);
    void drawLegend(QPainter &painter);
    void drawLegendCell(QPainter &painter, const Pie &pie, const QRectF &rcBound);
    void draw3DPieRectSide(QPainter &painter, const Pie &pie, QBrush &brush);
    void draw3DPieRectSide(QPainter &painter, const PieSide &pieSide, QBrush &brush);
    void draw3DPieArcSide(QPainter &painter, const Pie &pie, bool front=true);
    void drawEmptyPieChart(QPainter &painter);
    void showGridToolTip(const QPoint &pt);

    void calculatePieRect();//计算饼矩形范围
    void calculatePieData();//计算饼位置、百分比等
    void calculatePieLegendGrid();//计算图例表格宽度高度等
    void sortPieRectSide();//计算侧面的先后顺序
    QPainterPath make3DPieArcSidePath(double startAngle, double endAngle, const Pie &pie);//生成弧形侧面路径
    Pie *hitTest(QPoint pt);//判断是否点中
    double getMaxLegendWidth();//取得描述文字最宽的宽度
    void initDefaultColors();
    QColor getDefaultColor();
    QString getPieToolTip(const Pie &pie);
    QString getPieLabel(const Pie &pie);

private:
    QVector<Pie> m_pies;
    QVector<PieSide> m_pieSides;

    QString m_unit;
    int m_startAngle;
    double m_totalValue;

    bool m_isLegendVisible;
    bool m_isLabelVisible;
    bool m_isTransparentBg;
    bool m_isTurning;
    bool m_isClockWiseTurning;

    QPen m_textPen;
    QPen m_borderPen;

    QRectF m_chartRect;

    double m_3DoffsetY;
    double m_explodedRadius;

    double m_chartMargin; 
    double m_pieMargin;

    int m_pieColorAlpha; 
    int m_pieColorAlphaDark; 

    QRectF m_pieRect;
    QRectF m_pieShadowRect;

    QColor m_chartBgDarkColor;
    QColor m_chartBgLightColor;
    QColor m_chartBorderColor;
    double m_roundRadius;
    double m_pieWidthHeightRatio;

    int m_gridRowCount;
    int m_gridColCount;
    double m_gridCellWidth;
    double m_gridCellHeight;
    QRectF m_gridRect;

    QPoint m_mouseDownPoint;


    QVector<QColor> m_defaultColors;
};

#endif // Q3DPIECHART_H

 q3dpiechart.cpp

#include "q3dpiechart.h"


#include <math.h>
//#include <algorithm>


const double PI = 3.1415926535;
const double EPSELON = 0.00001;

q3dpiechart::q3dpiechart(QWidget* parent /* = NULL */)
    :QWidget(parent)
{
    m_startAngle = 0;
    m_totalValue = 0.0;

    m_isLegendVisible = true;
    m_isLabelVisible = true;
    m_isTransparentBg = false; 
    m_isTurning = false;
    m_isClockWiseTurning = true;

    m_gridRowCount = m_gridColCount = 1;

    m_textPen = QPen(QColor(0, 0, 0));
    m_borderPen = QPen(QColor::fromRgb(0, 0, 0, 64));

    m_pieColorAlpha = 152;
    m_pieColorAlphaDark = 200;

    m_chartBgDarkColor = QColor::fromRgb(213, 224, 241, 255);
    m_chartBgLightColor = QColor::fromRgb(255, 255, 255, 255);
    m_chartBorderColor = QColor::fromRgb(119, 141, 173);
    m_roundRadius = 10;

    m_chartMargin = 5;
    m_3DoffsetY = 20;

    m_pieMargin = 40;
    m_explodedRadius = 15;
    m_pieWidthHeightRatio = 2;

    initDefaultColors();

    startTimer(60);
}

q3dpiechart::~q3dpiechart()
{

}

void q3dpiechart::addPie(double v, const QString &desc)
{
    addPie(v, desc, getDefaultColor());
}

void q3dpiechart::addPie(double v, const QString &desc, const QColor &fillColor)
{
    Pie pie;
    pie.value = fabs(v);
    pie.description = desc;
    pie.brush = QBrush(fillColor);
    pie.isExploded = false;
    m_pies.append(pie);
    refreshChart();
}

void q3dpiechart::removePie(int pieIndex)
{
    if (pieIndex < m_pies.count())
    {
        m_pies.remove(pieIndex);
        refreshChart();
    }
}

void q3dpiechart::clear()
{
    if (m_pies.count() > 0)
    {
        m_pies.clear();
        refreshChart();
    }
}

void q3dpiechart::refreshChart()
{
    m_chartRect = rect().adjusted(m_chartMargin, m_chartMargin, -m_chartMargin, -m_chartMargin);

    m_totalValue = 0.0;

    for (QVector<Pie>::iterator itr = m_pies.begin();
        itr != m_pies.end(); itr++)
    {
        m_totalValue += itr->value;
    }
    if (m_isLegendVisible)
    {
        calculatePieLegendGrid();
    }
    calculatePieRect();
    calculatePieData();
    update();
}

void q3dpiechart::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    int pieCount = m_pies.count();

    if (!m_isTransparentBg)
    {
        drawBackground(painter);
    }

    if (m_pieRect.width() <=0 || m_pieRect.height() <=0)
    {
        return;
    }

    if(pieCount <= 0 || m_totalValue < EPSELON)
    {
        drawEmptyPieChart(painter);
        return;
    }

    //画底面
    for(int i=0; i<pieCount; i++)
    {
        const Pie &pie = m_pies[i];
        if (pie.percentValue >= EPSELON)
        {
            QColor clr = pie.brush.color();
            clr.setAlpha(m_pieColorAlpha);
            QBrush brush(clr);
            draw3DPieSuface(painter, pie, pie.boundShadowRect, brush, false);
        }

    }

    //画弧形侧面,背面的
    for(int i=0; i<pieCount; i++)
    {
        const Pie &pie = m_pies[i];
        if (pie.percentValue >= EPSELON)
        {
            draw3DPieArcSide(painter, pie, false);
        }
    }

    //画矩形侧面
    for(int i=0; pieCount>1 && i<pieCount; i++)
    {
        const PieSide &ps = m_pieSides[i];
        if (ps.pie->percentValue < EPSELON)
        {
            continue;
        }

        QColor clr = ps.pie->brush.color();
        clr.setAlpha(m_pieColorAlpha);
        clr.setAlpha(224);
        //clr = Qt::black;
        QBrush brush(clr);
        draw3DPieRectSide(painter, ps, brush);
    }

    //画弧形侧面,前面的
    for(int i=0; i<pieCount; i++)
    {
        const Pie &pie = m_pies[i];
        if (pie.percentValue >= EPSELON)
        {
            draw3DPieArcSide(painter, pie);
        }
    }

    //画表面
    for(int i=0; i<pieCount; i++)
    {
        const Pie &pie = m_pies[i];
        if (pie.percentValue >= EPSELON)
        {
            QColor clr = pie.brush.color();
            clr.setAlpha(m_pieColorAlphaDark);
            QBrush brush(clr);
            draw3DPieSuface(painter, pie, pie.boundRect, brush);

            if (m_isLabelVisible)
            {
                draw3DPieLabel(painter, pie);
            }
        }
    }

    if (m_isLegendVisible)
    {
        drawLegend(painter);
    }
}


void q3dpiechart::drawBackground(QPainter &painter)
{
    drawBackground3DShadow(painter, m_chartRect);

    QRectF &rect = m_chartRect;
    QPainterPath path;
    path.addRoundRect(rect, m_roundRadius);

    QLinearGradient gradient(rect.topLeft(), rect.bottomRight());
    gradient.setStart(rect.center().x(), rect.top());
    gradient.setFinalStop(rect.center().x(), rect.bottom());
    gradient.setColorAt(0, m_chartBgDarkColor);
    gradient.setColorAt(1, m_chartBgLightColor);
    QBrush brush(gradient);
    painter.fillPath(path, brush);

    QPen pen(m_chartBorderColor);
    pen.setWidth(2);
    painter.setPen(pen);
    painter.drawPath(path);
}

void q3dpiechart::drawBackground3DShadow(QPainter &painter, const QRectF &rect)
{
    float radius = rect.height() > rect.width() ? rect.width() / 20 : rect.height()/20;
    float offset = radius / 3;
    QColor clr(255, 255, 255, 0);
    QRectF rc = rect.translated(offset, offset);
    QRectF topRightArcRect = rc.adjusted(rc.width() - radius * 2, 0, 0, - rc.height() + radius * 2);

    QPainterPath topRightArcPath;
    topRightArcPath.moveTo(topRightArcRect.center());
    topRightArcPath.arcTo(topRightArcRect, 0, 90);
    QRadialGradient topRightRG(topRightArcRect.center(), radius);
    topRightRG.setColorAt(0, Qt::black);
    topRightRG.setColorAt(1, clr);
    QBrush topRightBrush(topRightRG);
    painter.fillPath(topRightArcPath, topRightBrush);

    QRectF rightRc = topRightArcRect.adjusted(radius, radius, 0, rect.height() - 3 * radius);
    QLinearGradient rightLG(rightRc.left(), 0, rightRc.right(), 0);
    rightLG.setColorAt(0, Qt::black);
    rightLG.setColorAt(1, clr);
    QBrush rightBrush(rightLG);
    QPainterPath rightPath;
    rightPath.addRect(rightRc);
    painter.fillPath(rightPath, rightBrush);

    QRectF leftBottomArcRect = rc.adjusted(0, rc.height() - radius * 2, -rc.width() + radius * 2, 0);
    QPainterPath leftBottomPath;
    leftBottomPath.moveTo(leftBottomArcRect.center());
    leftBottomPath.arcTo(leftBottomArcRect, -90, -180);
    QRadialGradient leftBottomRG(leftBottomArcRect.center(), radius);
    leftBottomRG.setColorAt(0, Qt::black);
    leftBottomRG.setColorAt(1, clr);
    QBrush leftBottomBrush(leftBottomRG);
    painter.fillPath(leftBottomPath, leftBottomBrush);

    QRectF bottomRc = leftBottomArcRect.adjusted(radius, radius, rect.width() - 3 * radius, 0);
    QLinearGradient bottomLG(0, bottomRc.top(), 0, bottomRc.bottom());
    bottomLG.setColorAt(0, Qt::black);
    bottomLG.setColorAt(1, clr);
    QBrush bottomBrush(bottomLG);
    QPainterPath bottomPath;
    bottomPath.addRect(bottomRc);
    painter.fillPath(bottomPath, bottomBrush);

    QRectF bottomRightArcRect = leftBottomArcRect.translated(rect.width() - radius * 2, 0);
    QPainterPath bottomRightPath;
    bottomRightPath.moveTo(bottomRightArcRect.center());
    bottomRightPath.arcTo(bottomRightArcRect, 0, -90);
    QRadialGradient bottomRightRG(bottomRightArcRect.center(), radius);
    bottomRightRG.setColorAt(0, Qt::black);
    bottomRightRG.setColorAt(1, clr);
    QBrush bottomRightBrush(bottomRightRG);
    painter.fillPath(bottomRightPath, bottomRightBrush);
}

void q3dpiechart::draw3DPieSuface(QPainter &painter, const Pie &pie, const QRectF &rcBound, const QBrush &brush, bool isTop/* = true*/)
{
    QPainterPath path;
    QPoint ptOffset;
    if (!isTop)
    {
        ptOffset.setY(m_3DoffsetY);
    }
    path.moveTo(rcBound.center());
    path.lineTo(pie.startPoint + ptOffset);
    path.arcTo(rcBound, pie.startAngle, pie.spanAngle);
    path.closeSubpath();

    painter.fillPath(path, brush);
    painter.setPen(m_borderPen);
    painter.drawArc(rcBound, pie.startAngle * 16, pie.spanAngle * 16);
}

void q3dpiechart::draw3DPieLabel(QPainter &painter, const Pie &pie)
{
    painter.setFont(this->font());
    painter.setPen(m_textPen);
    if (1 == m_pies.count())
    {
        painter.drawText(m_pieRect, Qt::AlignCenter, pie.percentStr);
        return;
    }

    QString label = getPieLabel(pie);
    QRectF rc;
    rc = QRectF(pie.centerPoint, pie.centerPoint);
    QFontMetricsF metrics(this->font());
    double quarterW = metrics.width(label) / 2;//m_pieRect.width() / 4;
    double halfH = metrics.height() / 2;//m_pieRect.height() / 2;
    rc.adjust(-quarterW, -halfH, quarterW, halfH);


    QPainterPath path;
    path.moveTo(pie.boundRect.center());
    path.lineTo(pie.startPoint);
    path.arcTo(pie.boundRect, pie.startAngle, pie.spanAngle);
    path.closeSubpath();

    if (path.contains(rc))
    {
        painter.drawText(rc, Qt::AlignCenter, label);
    }
    else
    {
        draw3DPieLabelOut(painter, pie, rc);
    }
}

void q3dpiechart::draw3DPieLabelOut(QPainter &painter, const Pie &pie, QRectF labelSizeRect)
{
    double centerAngle = pie.startAngle + pie.spanAngle / 2;
    if (centerAngle > 360)
    {
        centerAngle -= 360;
    }

    QPainterPath path;
    path.arcMoveTo(pie.boundRect, centerAngle);

    if (centerAngle >= 0 && centerAngle <90)
    {
        labelSizeRect.moveBottomLeft(path.currentPosition());
        painter.drawLine(pie.centerPoint, path.currentPosition());
    }
    else if (centerAngle >= 90 && centerAngle <180)
    {
        labelSizeRect.moveBottomRight(path.currentPosition());
        painter.drawLine(pie.centerPoint, path.currentPosition());
    }
    else if (centerAngle >= 180 && centerAngle <270)
    {
        labelSizeRect.moveTopRight(path.currentPosition());
        painter.drawLine(pie.centerPoint, labelSizeRect.bottomRight());
    }
    else if (centerAngle >= 270 && centerAngle <=360)
    {
        labelSizeRect.moveTopLeft(path.currentPosition());
        painter.drawLine(pie.centerPoint, labelSizeRect.bottomLeft());
    }
    painter.drawLine(labelSizeRect.bottomLeft(), labelSizeRect.bottomRight());
    painter.drawText(labelSizeRect, Qt::AlignLeft, getPieLabel(pie));
}

void q3dpiechart::drawLegend(QPainter &painter)
{

}

void q3dpiechart::drawLegendCell(QPainter &painter, const Pie &pie, const QRectF &rcBound)
{

}

void q3dpiechart::draw3DPieRectSide(QPainter &painter, const Pie &pie, QBrush &brush)
{
    QPainterPath path;
    QPoint offsetPt(0, m_3DoffsetY);
    path.moveTo(pie.boundRect.center());
    path.lineTo(pie.boundShadowRect.center());
    path.lineTo(pie.startPoint + offsetPt);
    path.lineTo(pie.startPoint);
    path.lineTo(pie.boundRect.center());
    painter.fillPath(path, brush);

    path = QPainterPath();
    path.moveTo(pie.boundRect.center());
    path.lineTo(pie.boundShadowRect.center());
    path.lineTo(pie.endPoint + offsetPt);
    path.lineTo(pie.endPoint);
    path.lineTo(pie.boundRect.center());

    painter.fillPath(path, brush);

    if (pie.spanAngle != 180)
    {
        painter.setPen(m_borderPen);
        painter.drawLine(pie.boundRect.center(), pie.boundShadowRect.center());
    }
}

void q3dpiechart::draw3DPieRectSide(QPainter &painter, const PieSide &pieSide, QBrush &brush)
{
    QPainterPath path;
    QPoint offsetPt(0, m_3DoffsetY);
    Pie &pie = *pieSide.pie;
    if (pie.startAngle == pieSide.angle)
    {
        path.moveTo(pie.boundRect.center());
        path.lineTo(pie.boundShadowRect.center());
        path.lineTo(pie.startPoint + offsetPt);
        path.lineTo(pie.startPoint);
        path.lineTo(pie.boundRect.center());
    }
    else
    {
        path.moveTo(pie.boundRect.center());
        path.lineTo(pie.boundShadowRect.center());
        path.lineTo(pie.endPoint + offsetPt);
        path.lineTo(pie.endPoint);
        path.lineTo(pie.boundRect.center());
    }
    painter.fillPath(path, brush);

    if (pie.spanAngle != 180)
    {
        painter.setPen(m_borderPen);
        painter.drawLine(pie.boundRect.center(), pie.boundShadowRect.center());
    }
}

void q3dpiechart::draw3DPieArcSide(QPainter &painter, const Pie &pie, bool front/*=true*/)
{
    QColor clrDark = pie.brush.color();
    double darkRate = 0.7;
    clrDark = QColor::fromRgb(clrDark.red() * darkRate, clrDark.green() * darkRate, clrDark.blue() * darkRate);
    clrDark.setAlpha(224);
    QColor clrLight = pie.brush.color();
    clrLight.setAlpha(0);
    clrLight = QColor::fromRgb(255, 255, 255, 128);

    QLinearGradient lg(pie.boundRect.left(), 0, pie.boundRect.right(), 0);
    lg.setColorAt(0, clrDark);
    lg.setColorAt(0.5, clrLight);
    lg.setColorAt(1, clrDark);
    QBrush frontBrush(lg);//正面的刷子为发光状
    QBrush backBrush(pie.brush.color());//背面的刷子不发光

    QPointF offsetPt(0, m_3DoffsetY);
    QPainterPath path;

    //存放拆分后的的路径和画刷对
    std::vector< QPair<QPainterPath, QBrush> > arcSides;
//    std::vector< std::pair<QPainterPath, QBrush> > arcSides;

    double endAngle = pie.startAngle + pie.spanAngle;

    //出现与水平面相交时,进行拆分
    if ((pie.startAngle<180 && endAngle > 180)
        ||(pie.startAngle>=180 && endAngle > 360))
    {
        double startAngle = pie.startAngle;//当前的起始角度
        if (pie.startAngle <180)//一二象限
        {
            path = make3DPieArcSidePath(startAngle, 180, pie);
            arcSides.push_back(QPair<QPainterPath, QBrush>(path, backBrush));
//            arcSides.push_back(std::make_pair<QPainterPath, QBrush>(path, backBrush));
            startAngle = 180;

            QBrush tempBrush = frontBrush;
            if (endAngle > 360)
            {
                path = make3DPieArcSidePath(startAngle, 360, pie);
                arcSides.push_back(QPair<QPainterPath, QBrush>(path, frontBrush));
//                arcSides.push_back(std::make_pair<QPainterPath, QBrush>(path, frontBrush));
                startAngle = 360;
                tempBrush = backBrush;
            }

            path = make3DPieArcSidePath(startAngle, endAngle, pie);
            arcSides.push_back(QPair<QPainterPath, QBrush>(path, tempBrush));
//            arcSides.push_back(std::make_pair<QPainterPath, QBrush>(path, tempBrush));
        }
        else//三四象限
        {
            path = make3DPieArcSidePath(startAngle, 360, pie);
            arcSides.push_back(QPair<QPainterPath, QBrush>(path, frontBrush));
//            arcSides.push_back(std::make_pair<QPainterPath, QBrush>(path, frontBrush));
            startAngle = 360;

            QBrush tempBrush = backBrush;
            if (endAngle > 540)
            {
                path = make3DPieArcSidePath(startAngle, 540, pie);
                arcSides.push_back(QPair<QPainterPath, QBrush>(path, backBrush));
//                arcSides.push_back(std::make_pair<QPainterPath, QBrush>(path, backBrush));
                startAngle = 540;
                tempBrush = frontBrush;
            }

            path = make3DPieArcSidePath(startAngle, endAngle, pie);
            arcSides.push_back(QPair<QPainterPath, QBrush>(path, tempBrush));
//            arcSides.push_back(std::make_pair<QPainterPath, QBrush>(path, tempBrush));
        }
    }
    else//不相交时
    {
        path = make3DPieArcSidePath(pie.startAngle, pie.startAngle + pie.spanAngle, pie);
//        arcSides.push_back(std::make_pair<QPainterPath, QBrush>(path, pie.startAngle >= 180 ? frontBrush : backBrush));
        arcSides.push_back(QPair<QPainterPath, QBrush>(path, pie.startAngle >= 180 ? frontBrush : backBrush));
    }
    for (int i=0; i<arcSides.size(); i++)
    {
        QPair<QPainterPath, QBrush> &arcSide = arcSides.at(i);
//        std::pair<QPainterPath, QBrush> &arcSide = arcSides.at(i);
        if ((front && arcSide.second == frontBrush)
            || (!front && arcSide.second == backBrush))
        {
            painter.fillPath(arcSide.first, arcSide.second);
        }
    }
}

QPainterPath q3dpiechart::make3DPieArcSidePath(double startAngle, double endAngle, const Pie &pie)
{
    QPainterPath path;
    path.arcMoveTo(pie.boundRect, startAngle);
    path.arcTo(pie.boundRect, startAngle, endAngle - startAngle);

    QPointF pt = path.currentPosition();
    pt.setY(pt.y() + m_3DoffsetY);
    path.lineTo(pt);
    path.arcTo(pie.boundShadowRect, endAngle, startAngle - endAngle);
    path.closeSubpath();
    return path;
}


void q3dpiechart::drawEmptyPieChart(QPainter &painter)
{
    QRectF rcBound = m_pieRect;
    rcBound.setHeight(rcBound.height() - m_3DoffsetY);

    QPointF pt1(rcBound.left(), rcBound.center().y());

    QPen borderPen(Qt::darkGray);
    painter.setPen(borderPen);
    painter.drawEllipse(rcBound);

    rcBound.translate(0, m_3DoffsetY);
    QPointF pt2(rcBound.left(), rcBound.center().y());

    painter.drawEllipse(rcBound);

    painter.drawLine(pt1, pt2);
    pt1.setX(pt1.x() + rcBound.width());
    pt2.setX(pt2.x() + rcBound.width());
    painter.drawLine(pt1, pt2);
}

void q3dpiechart::showGridToolTip(const QPoint &pt)
{

}

void q3dpiechart::calculatePieRect()
{
    
    const int legendWidth = m_gridRect.width();
    if (0 == m_pies.count() || !m_isLegendVisible)//没有添加内容或不显示描述信息,以整个区域为饼图区域
    {
        m_pieRect = m_chartRect.adjusted(m_pieMargin, m_pieMargin, -m_pieMargin, -m_pieMargin);
    }
    else
    {
        m_pieRect = m_chartRect.adjusted(m_pieMargin, m_pieMargin, -m_pieMargin, -m_pieMargin - legendWidth);
    }

    double w = m_pieRect.width();
    double h = m_pieRect.height();

    if (h <= 0 || w <=0)
    {
        m_pieRect.setRect(0, 0, 0, 0);
        return;
    }

    if (w / h > m_pieWidthHeightRatio)
    {
        double half = (w - h * m_pieWidthHeightRatio) / 2;
        m_pieRect.adjust(half, 0, -half, 0);
    }
    else
    {
        double half = (h - w / m_pieWidthHeightRatio) / 2;
        m_pieRect.adjust(0, half, 0, -half);
    }

    m_3DoffsetY = m_pieRect.height() / 5;
    double maxOffsetY = m_pieMargin * 2;
    if (m_3DoffsetY > maxOffsetY)
    {
        m_3DoffsetY = maxOffsetY;
    }

    m_explodedRadius = m_pieRect.height() / 6;
    double maxExplodedRadius = m_pieMargin * 1.5;
    if (m_explodedRadius > maxExplodedRadius)
    {
        m_explodedRadius = maxExplodedRadius;
    }

    m_pieShadowRect = m_pieRect.translated(0, m_3DoffsetY);
}

void q3dpiechart::calculatePieData()
{
    double startAngle = m_startAngle;
    double quarterW = m_pieRect.width() / 6;
    double quarterH = m_pieRect.height() / 6;
    QRectF halfPieRect = m_pieRect.adjusted(quarterW, quarterH, -quarterW, -quarterH);
    QRectF explodedRect(m_pieRect.center(), m_pieRect.center());
    explodedRect.adjust(-m_explodedRadius, -m_explodedRadius, m_explodedRadius, m_explodedRadius);
    for (int i=0; i<m_pies.count(); i++)
    {
        Pie &pie = m_pies[i];
        pie.startAngle = startAngle;

        QPainterPath path;
        path.arcMoveTo(m_pieRect, startAngle);
        pie.startPoint = path.currentPosition();//起始点

        if (m_totalValue < EPSELON)
        {
            pie.percentValue = 0;//1.0 / m_pies.count();
        }
        else
        {
            pie.percentValue = pie.value / m_totalValue;//所占比例
        }
        pie.percentStr = QString("%1%").arg(pie.percentValue*100, 3, 'G', 3);

        pie.spanAngle = 360 * pie.percentValue;//跨度
        if (i == m_pies.count()-1)//最后一个饼,用360度减去前面的总度数,以防止由于误差导致的合不上
        {
            pie.spanAngle = 360 - (startAngle - m_startAngle);
        }

        path.arcTo(m_pieRect, startAngle, pie.spanAngle);
        pie.endPoint = path.currentPosition();

        path.arcMoveTo(halfPieRect, startAngle + pie.spanAngle / 2);
        pie.centerPoint = path.currentPosition();

        if (pie.isExploded)
        {
            path.arcMoveTo(explodedRect, startAngle + pie.spanAngle / 2);
            QPointF pt = path.currentPosition() - m_pieRect.center();
            pie.centerPoint += pt;
            pie.startPoint += pt;
            pie.endPoint += pt;
            pie.boundRect = m_pieRect.translated(pt);
        }
        else
        {
            pie.boundRect = m_pieRect;
        }
        pie.boundShadowRect = pie.boundRect.translated(0, m_3DoffsetY);

        startAngle += pie.spanAngle;
        while (pie.startAngle > 360)
        {
            pie.startAngle -= 360;
        }
    }

    sortPieRectSide();
}

void q3dpiechart::calculatePieLegendGrid()
{
    QFontMetricsF metrics(this->font());
    m_gridCellHeight = metrics.height() * 2;
    m_gridCellWidth = getMaxLegendWidth();//metrics.width("123456789012345");
    m_gridCellWidth += m_gridCellHeight + m_gridCellHeight / 4;//描述前留下一个高度的方块,用于显示颜色,后面留下1/4高度的宽度,以防止抵到表格右边
    if (m_gridCellWidth > m_chartRect.width() / 2)
    {
        m_gridCellWidth = m_chartRect.width() / 2;
    }

    m_gridColCount = m_pies.count();
    m_gridRowCount = 1;

    m_gridRect = m_chartRect.adjusted(m_pieMargin, m_pieMargin, -m_pieMargin, -m_pieMargin);
    m_gridRect.setLeft(m_gridRect.width() - m_gridCellWidth);
}

//排序规则:0-180度的越靠近90度越在后面(显示顺序),180-360的越靠近270度越在前面(显示顺序)
bool pieSideLesTthan(const PieSide &s1, const PieSide &s2)
{
    if (s1.angle <180)
    {
        if (s2.angle >=180)
        {
            return true;
        }
        else
        {
            return fabs(s1.angle - 90) < fabs(s2.angle - 90);
        }
    }
    else
    {
        if (s2.angle >=180)
        {
            return fabs(s1.angle - 270) > fabs(s2.angle - 270);
        }
        else
        {
            return false;
        }
    }
}

void q3dpiechart::sortPieRectSide()
{
    m_pieSides.clear();
    for (int i=0; i<m_pies.count(); i++)
    {
        Pie &pie = m_pies[i];
        double startAngle = pie.startAngle;
        double endAngle = startAngle + pie.spanAngle;
        if (endAngle>=360)
        {
            endAngle-=360;
        }

        //每个Pie有两边
        PieSide startSide = {&pie, startAngle};
        PieSide endSide = {&pie, endAngle};
        m_pieSides.push_back(startSide);
        m_pieSides.push_back(endSide);
    }

    qSort(m_pieSides.begin(), m_pieSides.end(), pieSideLesTthan);
}

Pie *q3dpiechart::hitTest(QPoint pt)
{
    //优先判断是否点中上表面和三四象限的弧侧面
    for (int i=0; i<m_pies.count(); i++)
    {
        Pie &pie = m_pies[i];

        QPainterPath path;
        path.moveTo(pie.boundRect.center());
        path.arcMoveTo(pie.boundRect, pie.startAngle);
        path.lineTo(path.currentPosition());
        path.arcTo(pie.boundRect, pie.startAngle, pie.spanAngle);
        path.lineTo(pie.boundRect.center());

        //点在了正面
        if (path.contains(pt))
        {
            return &pie;
        }

        //如果是点在180-360的弧形侧面,也算点中了,分几种情况进行判断
        double endAngle = pie.startAngle + pie.spanAngle;
        if (pie.startAngle < 180 && endAngle >180)
        {
            if (make3DPieArcSidePath(180, qMin(360.0, endAngle), pie).contains(pt))
            {
                return &pie;
            }
        }
        else if (pie.startAngle >180)
        {
            if (make3DPieArcSidePath(pie.startAngle, qMin(360.0, endAngle), pie).contains(pt))
            {
                return &pie;
            }

            if (endAngle > 540)
            {
                if (make3DPieArcSidePath(540, endAngle, pie).contains(pt))
                {
                    return &pie;
                }
            }
        }
    }

    //再判断Pie的矩形侧面
    for (int i=m_pieSides.count()- 1; i>-1; i--)
    {
        PieSide &pieSide = m_pieSides[i];
        Pie &pie = *pieSide.pie;
        QPainterPath path;
        QPoint offsetPt(0, m_3DoffsetY);

        if (pie.startAngle == pieSide.angle)
        {
            path.moveTo(pie.boundRect.center());
            path.lineTo(pie.boundShadowRect.center());
            path.lineTo(pie.startPoint + offsetPt);
            path.lineTo(pie.startPoint);
            path.lineTo(pie.boundRect.center());
            if (path.contains(pt))
            {
                return &pie;
            }
        }
        else
        {
            path.moveTo(pie.boundRect.center());
            path.lineTo(pie.boundShadowRect.center());
            path.lineTo(pie.endPoint + offsetPt);
            path.lineTo(pie.endPoint);
            path.lineTo(pie.boundRect.center());

            if (path.contains(pt))
            {
                return &pie;
            }
        }
    }

    return NULL;
}

double q3dpiechart::getMaxLegendWidth()
{
    return 0;
}

void q3dpiechart::mousePressEvent(QMouseEvent *event)
{
    m_mouseDownPoint = event->globalPos();
}

void q3dpiechart::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton 
        && m_pies.count() >1 && m_mouseDownPoint == event->globalPos())
    {
        Pie *pie = hitTest(event->pos());

        if (pie != NULL)
        {
            pie->isExploded = !pie->isExploded;
            refreshChart();
        }
    }
}

void q3dpiechart::mouseMoveEvent(QMouseEvent *event)
{
    //Pie *pie = hitTest(event->pos());

    //if (pie != NULL)
    //{
    //    setToolTip(getPieToolTip(*pie));
    //}
}

QString q3dpiechart::getPieToolTip(const Pie &pie)
{
    return QString("<font color=red>%1</font>:<font color=green>%2</font><font color=blue>(%3)</font>")
        .arg(pie.description)
        .arg(pie.value/*, 0, 'f', 2*/)
        .arg(pie.percentStr);
}

QString q3dpiechart::getPieLabel(const Pie &pie)
{
    return QString("%1:%2").arg(pie.description).arg(pie.percentStr);
}


void q3dpiechart::resizeEvent(QResizeEvent *event)
{
    refreshChart();
}

void q3dpiechart::timerEvent(QTimerEvent * event)
{
    if (m_isTurning)
    {
        m_startAngle += m_isClockWiseTurning ? 1 : -1;

        if (m_startAngle < 0)
        {
            m_startAngle += 360;
        }
        else if (m_startAngle > 360)
        {
            m_startAngle -= 360;
        }

        calculatePieData();
        update();
    }
}

void q3dpiechart::initDefaultColors()
{
    m_defaultColors.clear();
    m_defaultColors.append(QColor(192, 0, 0, 255));
    m_defaultColors.append(QColor(0, 192, 0, 255));
    m_defaultColors.append(QColor(192, 192, 0, 255));
    m_defaultColors.append(QColor(192, 0, 192, 255));
    m_defaultColors.append(QColor(0, 192, 192, 255));
    int r = 0;
    int g = 0;
    int b = 192;
    for (int i=0; i<20; i++)
    {
        m_defaultColors.append(QColor(r, g, b, 255));

        r += 48;
        g += 24;
        b += 24;
        if (r > 255)
        {
            r -=255;
        }
        if (g > 255)
        {
            g -= 255;
        }
        if (b > 255)
        {
            b -= 255;
        }
    }
}

QColor q3dpiechart::getDefaultColor()
{
    if (m_defaultColors.count() == 0)
    {
        return Qt::blue;
    }
    int index = m_pies.count();
    if (index >= m_defaultColors.count())
    {
        index = 0;
    }
    return m_defaultColors[index];
}

q3dpiechart.pro文件

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11
TEMPLATE = app
#TARGET = Automation

FORMS += \
    q3dpiechart_demo.ui

HEADERS += \
    ../q3dpiechart/pie.h \
    ../q3dpiechart/q3dpiechart.h \
    q3dpiechart_demo.h

SOURCES += \
    ../q3dpiechart/q3dpiechart.cpp \
    main.cpp \
    q3dpiechart_demo.cpp

q3dpiechart_demo.cpp

#include "q3dpiechart_demo.h"

#include "../q3dpiechart/q3dpiechart.h"

#include <QBoxLayout>

q3dpiechart_demo::q3dpiechart_demo(QWidget *parent, Qt::WindowFlags flags)
	: QDialog(parent, flags)
{
	ui.setupUi(this);

    q3dpiechart *piechart = new q3dpiechart(this);
    piechart->addPie(10, "aaa");
    piechart->addPie(13, "bbb");
    piechart->addPie(11, "ccc");


    QHBoxLayout *layout = new QHBoxLayout(this);

    layout->addWidget(piechart);
    setLayout(layout);
    
}

q3dpiechart_demo::~q3dpiechart_demo()
{

}

q3dpiechart_demo.h

#ifndef Q3DPIECHART_DEMO_H
#define Q3DPIECHART_DEMO_H

#include <QDialog>
#include "ui_q3dpiechart_demo.h"

class q3dpiechart_demo : public QDialog
{
	Q_OBJECT

public:
    q3dpiechart_demo(QWidget *parent = 0, Qt::WindowFlags flags = 0);
	~q3dpiechart_demo();

private:
	Ui::q3dpiechart_demoClass ui;
};

#endif // Q3DPIECHART_DEMO_H

q3dpiechart_demo.ui

<ui version="4.0" >
 <class>q3dpiechart_demoClass</class>
 <widget class="QDialog" name="q3dpiechart_demoClass" >
  <property name="geometry" >
   <rect>
    <x>0</x>
    <y>0</y>
    <width>652</width>
    <height>483</height>
   </rect>
  </property>
  <property name="windowTitle" >
   <string>q3dpiechart_demo</string>
  </property>
 </widget>
 <layoutdefault spacing="6" margin="11" />
 <resources/>
 <connections/>
</ui>

main.cpp文件 

#include <QApplication>
#include "q3dpiechart_demo.h"

int main(int argc, char *argv[])
{
	QApplication a(argc, argv);
	q3dpiechart_demo w;
	w.show();
	return a.exec();
}

 qt 3d 饼图 下载链接:

https://download.csdn.net/download/weixin_41882459/90407325