QPainter绘制3D 饼状图

先展示图片

核心代码如下:

pie.h

cpp 复制代码
#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

cpp 复制代码
#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

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文件

cpp 复制代码
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

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

cpp 复制代码
#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

cpp 复制代码
<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文件

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

相关推荐
dot to one1 小时前
Qt 中 QWidget涉及的常用核心属性介绍
开发语言·c++·qt
码农新猿类2 小时前
初入OpenCV
qt·opencv·计算机视觉
洛克希德马丁4 小时前
QLineEdit增加点击回显功能
c++·qt·ui
向日葵xyz5 小时前
Qt5与现代OpenGL学习(十一)OpenGL Widget鼠标控制直线旋转
开发语言·qt·学习
小宋加油啊7 小时前
Mac QT水平布局和垂直布局
开发语言·qt·macos
小小鱼儿小小林8 小时前
用AI制作黑神话悟空质感教程,3D西游记裸眼效果,西游人物跳出书本
人工智能·3d·ai画图
伐尘18 小时前
【Qt】编译 Qt 5.15.x For Windows 基础教程 Visual Studio 2019 MSVC142 x64
windows·qt·visual studio
吃面不喝汤6619 小时前
破解 Qt QProcess 在 Release 模式下的“卡死”之谜
开发语言·qt
爱凤的小光21 小时前
图漾相机——Sample_V2示例程序(待补充)
3d·图漾相机
那就举个栗子!1 天前
3DGS-to-PC:3DGS模型一键丝滑转 点云 or Mesh 【Ubuntu 20.04】【2025最新版!!】
3d·三维重建