当前位置: 首页 > news >正文

QPainter绘制3D 饼状图

先展示图片 

核心代码如下:

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 guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11
TEMPLATE = app
#TARGET = AutomationFORMS += \q3dpiechart_demo.uiHEADERS += \../q3dpiechart/pie.h \../q3dpiechart/q3dpiechart.h \q3dpiechart_demo.hSOURCES += \../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_OBJECTpublic: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

相关文章:

QPainter绘制3D 饼状图

先展示图片 核心代码如下&#xff1a; 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 p…...

【FAQ】HarmonyOS SDK 闭源开放能力 —Live View Kit (1)

1.问题描述&#xff1a; 客户端创建实况窗后&#xff0c;通过Push kit更新实况窗内容&#xff0c;这个过程是自动更新的还是客户端解析push消息数据后填充数据更新&#xff1f;客户端除了接入Push kit和创建实况窗还需要做什么工作&#xff1f; 解决方案&#xff1a; 通过Pu…...

数据治理与管理

引入 上一篇我们聊了数仓架构设计,它是企业构建数据中台的基石。其本质就是构建一个可靠易用的架构,可以借此将原始数据汇聚、处理,最终转换成可消费使用的数据资源。 在拥有数据资源以后,我们就需要考虑如何利用它,为企业创造价值,让它变成企业的资产而不是负担。也就…...

什么是HTTP/2协议?NGINX如何支持HTTP/2并提升网站性能?

HTTP/2是一种用于在Web浏览器和服务器之间进行通信的协议&#xff0c;旨在提高网站性能和加载速度。它是HTTP/1.1的继任者&#xff0c;引入了许多优化和改进&#xff0c;以适应现代Web应用的需求。HTTP/2的主要目标是减少延迟、提高效率&#xff0c;以及更好地支持并发请求。 …...

安全运维,等保测试常见解决问题。

1. 未配置口令复杂度策略。 # 配置密码安全策略 # vi /etc/pam.d/system-auth # local_users_only 只允许本机用户。 # retry 3 最多重复尝试3次。 # minlen12 最小长度为12个字符。 # dcredit-1 至少需要1个数字字符。 # ucredit-1 至少需要1个大…...

jmeter接口测试(二)

一、不同参数类型的接口测试 二、动态参数接口处理 随机数 工具——>函数助手对话框&#xff08;Random 1000-10000之间的随机数 变量名为rdn&#xff09;如下图所示 把上图生成的函数字符串复制到想要使用的地方如下图 三、断言 1、状态断言&#xff0c;200 不能证明…...

Keil ARM Complier Missing Compiler Version 5

使用Keil软件时出现了编译时报错,找不到对应的ARM版本,报错Target Target 1 uses ARM-Compiler Default Compiler Version 5 which is not available. *** Please review the installed ARM Compiler Versions: Manage Project Items - Folders/Extensions to manage ARM Compi…...

【僵尸进程】

【僵尸进程】 目录&#xff1a;知识点1. 僵尸进程的定义2. 僵尸进程产生的原因3. 僵尸进程的危害4. 如何避免僵尸进程 代码示例产生僵尸进程的代码示例避免僵尸进程的代码示例&#xff08;父进程主动回收&#xff09;避免僵尸进程的代码示例&#xff08;信号处理&#xff09; 运…...

【框架】参考 Spring Security 安全框架设计出,轻量化高可扩展的身份认证与授权架构

关键字&#xff1a;AOP、JWT、自定义注解、责任链模式 一、Spring Security Spring Security 想必大家并不陌生&#xff0c;是 Spring 家族里的一个安全框架&#xff0c;特别完善&#xff0c;但学习成本比较大&#xff0c;不少开发者都觉得&#xff0c;这个框架“很重” 他的…...

【Git 学习笔记_27】DIY 实战篇:利用 DeepSeek 实现 GitHub 的 GPG 密钥创建与配置

文章目录 1 前言2 准备工作3 具体配置过程3.1. 本地生成 GPG 密钥3.2. 导出 GPG 密钥3.3. 将密钥配置到 Git 中3.4. 测试提交 4 问题排查记录5 小结与复盘 1 前言 昨天在更新我的第二个 Vim 专栏《Mastering Vim (2nd Ed.)》时遇到一个经典的 Git 操作问题&#xff1a;如何在 …...

微信小程序地图map全方位解析

微信小程序地图map全方位解析 微信小程序的 <map> 组件是一个功能强大的工具&#xff0c;可以实现地图展示、定位、标注、路径规划等多种功能。以下是全方位解析微信小程序地图组件的知识点&#xff1a; 一、地图组件基础 1. 引入 <map> 组件 在页面的 .wxml 文…...

调试无痛入手

在调试过程中&#xff0c;Step In、Step Over 和 Step Out 是控制代码执行流程的常用操作&#xff0c;帮助开发者逐行或逐块检查代码行为。以下是它们的详细介绍及使用方法&#xff1a; 1. Step In 功能&#xff1a;进入当前行的函数或方法内部&#xff0c;逐行执行其代码。使…...

【蓝桥杯集训·每日一题2025】 AcWing 6135. 奶牛体检 python

6135. 奶牛体检 Week 1 2月21日 农夫约翰的 N N N 头奶牛站成一行&#xff0c;奶牛 1 1 1 在队伍的最前面&#xff0c;奶牛 N N N 在队伍的最后面。 农夫约翰的奶牛也有许多不同的品种。 他用从 1 1 1 到 N N N 的整数来表示每一品种。 队伍从前到后第 i i i 头奶牛的…...

AI发展迅速,是否还有学习前端的必要性?

今天有个小伙伴跟我讨论&#xff1a;“现在 AI 发展迅速&#xff0c;是否还有学习 JS 或者 TS 及前端知识的必要&#xff1f;” 我非常肯定地说&#xff1a; 是的&#xff0c;学习 JavaScript/TypeScript 以及前端知识仍然非常必要&#xff0c;而且在可预见的未来&#xff0c;…...

【数据标准】数据标准化是数据治理的基础

导读&#xff1a;数据标准化是数据治理的基石&#xff0c;它通过统一数据格式、编码、命名与语义等&#xff0c;全方位提升数据质量&#xff0c;确保准确性、完整性与一致性&#xff0c;从源头上杜绝错误与冲突。这不仅打破部门及系统间的数据壁垒&#xff0c;极大促进数据共享…...

VS2022配置FFMPEG库基础教程

1 简介 1.1 起源与发展历程 FFmpeg诞生于2000年&#xff0c;由法国工程师Fabrice Bellard主导开发&#xff0c;其名称源自"Fast Forward MPEG"&#xff0c;初期定位为多媒体编解码工具。2004年后由Michael Niedermayer接任维护&#xff0c;逐步发展成为包含音视频采…...

three.js之特殊材质效果

*案例42 创建一个透明的立方体 <template><div ref"container" className"container"></div> </template><script setup> import * as THREE from three; import WebGL from three/examples/jsm/capabilities/WebGL.js // 引…...

Qt常用控件之日历QCalendarWidget

日历QCalendarWidget QCalendarWidget 是一个日历控件。 QCalendarWidget属性 属性说明selectDate当前选中日期。minimumDate最小日期。maximumDate最大日期。firstDayOfWeek设置每周的第一天是周几&#xff08;影响日历的第一列是周几&#xff09;。gridVisible是否显示日历…...

vxe-table 如何实现跟 Excel 一样的数值或金额的负数自动显示红色字体

vxe-table 如何实现跟 Excel 一样的数值或金额的负数自动显示红色字体&#xff0c;当输入的值为负数时&#xff0c;会自动显示红色字体&#xff0c;对于数值或者金额输入时该功能就非常有用了。 查看官网&#xff1a;https://vxetable.cn gitbub&#xff1a;https://github.co…...

DINOv2 + yolov8 + opencv 检测卡车的可拉拽雨覆是否完全覆盖

最近是接了一个需求咨询图像处理类的&#xff0c;甲方要在卡车过磅的地方装一个摄像头用检测卡车的车斗雨覆是否完全&#xff0c; 让我大致理了下需求并对技术核心做下预研究 开发一套图像处理软件&#xff0c;能够实时监控经过的卡车并判断其车斗的雨覆状态。 系统需具备以下…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

使用分级同态加密防御梯度泄漏

抽象 联邦学习 &#xff08;FL&#xff09; 支持跨分布式客户端进行协作模型训练&#xff0c;而无需共享原始数据&#xff0c;这使其成为在互联和自动驾驶汽车 &#xff08;CAV&#xff09; 等领域保护隐私的机器学习的一种很有前途的方法。然而&#xff0c;最近的研究表明&…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...