Qt 实现3维饼状图
一.思路
自从Qt5 引入 QChart 模块后,qt就大大增强了画图表的功能,如饼状图、柱形图、折线图、面积图等等。要知道,qt4时代,画个图表要不通过QPainter来画,要不通过第三方库来画,都是比较麻烦的。而qt5可以轻松画出二维的图表。
那么,如何画3D的饼状图图呢,此处介绍一种比较神奇的方法:
我们知道,一个圆柱体,可以看成是N个圆在Z轴方向叠加而成,所以,要画一个高度为H的圆柱体,那么,只需要画H个依次向上叠加的圆即可,再配以不同的颜色。
二.实现
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPainter>
#define PI_VALUE 3.1415926
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
void drawText(QPainter *painter,QRectF rect,int startAngle,int spanAngle,QString text);
protected:
void paintEvent( QPaintEvent* ev );
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <math.h>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
//画文本
//关键是求文本起始坐标位置。此处,取扇形中分线上的中点。
void MainWindow::drawText(QPainter *painter,QRectF rect, int startAngle, int spanAngle, QString text)
{
double rX = rect.width() / 2; //圆弧半径 x方向
double rY = rect.height() / 2; //圆弧半径 Y方向
//圆心
double x0 = rect.x() + rX;
double y0 = rect.y() + rY;
qDebug()<<"_______________________";
qDebug()<<"x0,y0: "<<x0<<y0;
double mx,my; //画文字的坐标位置
int endAngle = startAngle + spanAngle / 2;
if(spanAngle > 360)
spanAngle -= 360;
qDebug()<<"endAngle: "<<endAngle;
if(endAngle == 0)
{
mx = x0 + rX / 2;
my = y0;
}
else if(endAngle == 180)
{
mx = x0 - rX / 2;
my = y0;
}
else if(endAngle == 90)
{
mx = x0;
my = y0 - rY / 2;
}
else if(endAngle == 270)
{
mx = x0;
my = y0 + rY / 2;
}
else if(endAngle > 0 && endAngle < 90)
{
mx = x0 + rX * cos(endAngle * PI_VALUE / 180) / 2;
my = y0 - rY * sin(endAngle * PI_VALUE / 180) / 2;
}
else if(endAngle > 90 && endAngle < 180)
{
mx = x0 - rX * cos((180 - endAngle) * PI_VALUE / 180) / 2;
my = y0 - rY * sin((180 - endAngle) * PI_VALUE / 180) / 2;
}
else if(endAngle > 180 && endAngle < 270)
{
mx = x0 - rX * cos((270 - endAngle) * PI_VALUE / 180) / 2;
my = y0 + rY * sin((270 - endAngle) * PI_VALUE / 180) / 2;
}
else if(endAngle > 270 && endAngle < 360)
{
mx = x0 + rX * cos((360 - endAngle) * PI_VALUE / 180) / 2;
my = y0 + rY * sin((360 - endAngle) * PI_VALUE / 180) / 2;
}
else
{
return;
}
int textSpace = 5; //Y轴向下偏移一点点,看起来更加好
painter->drawText(mx, my + textSpace, text);
}
void MainWindow::paintEvent(QPaintEvent *ev)
{
QPainter painter(this);
// 去除画笔
painter.setPen(Qt::NoPen);
// 设置反锯齿
painter.setRenderHint(QPainter::Antialiasing);
//******************************************** 圆饼图 *****************************************//
int widWidth = this->width();
int widHeight = this->height();
int spaceH = 10;
int spaceV = 0;
int rectHeight = widHeight * 3 / 4; //圆弧所在矩形高度
int chartWidth = widWidth - 2 * spaceH;
int chartHeight = widHeight - rectHeight - 2 * spaceV - spaceV;
int normal = 20; //正常
int offline = 4; //离线
int abnormal = 3; //异常
int total = normal + offline + abnormal;
//颜色
QColor colorNormal = QColor(27,203,204);
QColor colorDarkNormal = QColor(36,124,121);
QColor colorOffline = QColor(225,180,225);
QColor colorDarkOffline = QColor(144,118,144);
QColor colorAbnormal = QColor(219,224,120);
QColor colorDarkAbnormal = QColor(142,144,77);
// 顶层圆面
QRectF rect_top(spaceH, spaceV, chartWidth, rectHeight);
// 底层圆面
QRectF rect_bottom(spaceH, widHeight - rectHeight - 2 * spaceV, chartWidth, rectHeight);
painter.setBrush(colorDarkNormal);
// 绘制底层圆面
painter.drawEllipse(rect_bottom);
//计算3种情况扇形图的起始角和旋转角
int startAngle_1 = 0;
int spanAngle_1 = 360 * normal / total;
int startAngle_2 = startAngle_1 + spanAngle_1;
int spanAngle_2 = 360 * offline / total;
int startAngle_3 = startAngle_2 + spanAngle_2;
int spanAngle_3 = 360 - spanAngle_1 - spanAngle_2;
if(spanAngle_3 < 0)
spanAngle_3 = 0;
//循环画 chartHeight 个 扇形(虽然效率有点低哈)
for(int i=0; i<chartHeight; i++)
{
QRectF rect(rect_bottom.x(),rect_bottom.y() - i,rect_bottom.width(),rect_bottom.height());
if(normal > 0)
{
painter.setBrush(colorDarkNormal);
painter.drawPie(rect, startAngle_1 * 16, spanAngle_1 * 16);
}
if(offline > 0)
{
painter.setBrush(colorDarkOffline);
painter.drawPie(rect, startAngle_2 * 16, spanAngle_2 * 16);
}
if(abnormal > 0)
{
painter.setBrush(colorDarkAbnormal);
painter.drawPie(rect, startAngle_3 * 16, spanAngle_3 * 16);
}
}
// 绘制顶层圆面
painter.setBrush(colorNormal);
painter.drawEllipse(rect_top);
// 绘制顶层扇形
if(normal > 0)
{
painter.setBrush(colorNormal);
painter.drawPie(rect_top, startAngle_1 * 16, spanAngle_1 * 16);
}
if(offline > 0)
{
painter.setBrush(colorOffline);
painter.drawPie(rect_top, startAngle_2 * 16, spanAngle_2 * 16);
}
if(abnormal > 0)
{
painter.setBrush(colorAbnormal);
painter.drawPie(rect_top, startAngle_3 * 16, spanAngle_3 * 16);
}
//画图例........................
QPainter painterText(this);
QFont font;
font.setBold(true);
font.setPixelSize(18);
painterText.setFont(font);
painterText.setPen(QColor(255,255,255));
if(normal > 0)
drawText(&painterText,rect_top,startAngle_1,spanAngle_1,QString::number(normal));
if(offline > 0)
drawText(&painterText,rect_top,startAngle_2,spanAngle_2,QString::number(offline));
if(abnormal > 0)
drawText(&painterText,rect_top,startAngle_3,spanAngle_3,QString::number(abnormal));
QWidget::paintEvent(ev);
}
三.效果图
如下,缩放窗口时,图像也会同步改变。