这几天自己绘制了一些小部件,主要就是重写paintEvent()函数,这其中会涉及到坐标系相关内容,这次以绘制八卦图为契机,了解并掌握绘制事件中的坐标平移与旋转。
希望这个例子能起到抛砖引玉的作用,同时也希望对Qt有兴趣的伙伴们一起进步。
“易有太极,是生两仪,两仪生四象,四象生八卦。”
大家对这句话应该都挺熟,我就不多说了。
八卦即八个卦相,分别为乾、坤、震、艮、离、坎、兑、巽。
八卦图分为伏羲八卦与文王八卦,也叫先天八卦与后天八卦。
这些大家应该也很熟,我就不卖弄啦。
刚开始没有发现两条鱼的界限部分绘制反了,应该是反S形的,结果绘制成了S形了。道法自然,赶紧改正。
修改前的效果:
修改后的效果:
yinyang.h
/*******************************************************************************
* @brief the Eight Diagrams
* @details 通过绘制阴阳八卦,掌握对坐标系的平移与旋转
* @author wujz
* @date 2020-10-22 09:56:36
* @version 1.0.0
* @par Copyright (c):
* @par History:
*******************************************************************************/
#ifndef YINYANG_H
#define YINYANG_H
#include
class Yinyang : public QWidget
{
Q_OBJECT
public:
/* 八卦样式 */
enum class EC_STYLE{
XIAN_TIAN, /* 伏羲先天八卦 */
HOU_TIAN /* 文王后天八卦 */
};
/* 爻 */
enum class EC_YAO {
YANG_YAO, /* 阳爻 */
YIN_YAO /* 阴爻 */
};
/* 卦 */
enum class EC_DIAGRAMS {
QIAN, /* 乾三连 */
KUN, /* 坤六断 */
ZHEN, /* 震仰盂 */
GEN, /* 艮覆碗 */
LI, /* 离中虚 */
KAN, /* 坎中满 */
DUI, /* 兑上缼 */
XUN /* 巽下断 */
};
explicit Yinyang(QWidget *parent = nullptr);
virtual ~Yinyang();
void set_angle(int angle);
void set_radius(int radius);
void set_style(EC_STYLE style);
void set_yang_yao_length(int len);
void set_yao_height(int height);
void set_yao_space(int space);
protected:
void paintEvent(QPaintEvent * event) override;
void draw_yin_yang_yao(QPainter & painter, const QPointF & p1,
const QPointF & p2, EC_YAO yao); /* 绘制爻 */
void draw_diagram(QPainter & painter, EC_DIAGRAMS diagram); /* 绘制卦 */
void draw_the_eight_diagrams(QPainter & painter); /* 绘制八卦图 */
void draw_tai_ji(QPainter & painter); /* 绘制太极图 */
signals:
private:
EC_STYLE my_style;
int my_angle; /* 旋转的角度 */
int my_radius; /* 太极图半径 */
QTimer * my_timer; /* 让太极图动起来 */
int my_yang_yao_len; /* 阳爻长度 */
int my_yao_height; /* 爻高度 */
int my_yao_space; /* 卦中爻间距 */
};
#endif // YINYANG_H
yinyang.cpp
#include
#include
#include
#include "yinyang.h"
Yinyang::Yinyang(QWidget *parent)
: QWidget(parent)
, my_style(EC_STYLE::XIAN_TIAN)
, my_angle(0)
, my_radius(100)
, my_yang_yao_len(70)
, my_yao_height(10)
, my_yao_space(10)
{
my_timer = new QTimer(0);
my_timer->setInterval(200);
connect(my_timer, &QTimer::timeout, [=]{
my_angle += 5;
my_angle %= 360;
update();
});
my_timer->start();
}
Yinyang::~Yinyang()
{
if (my_timer) {
if (my_timer->isActive())
my_timer->stop();
delete my_timer;
my_timer = nullptr;
}
}
void Yinyang::set_angle(int angle)
{
my_angle = angle % 360;
}
void Yinyang::set_radius(int radius)
{
my_radius = radius;
}
void Yinyang::set_style(Yinyang::EC_STYLE style)
{
my_style = style;
}
void Yinyang::set_yang_yao_length(int len)
{
my_yang_yao_len = len;
}
void Yinyang::set_yao_height(int height)
{
my_yao_height = height;
}
void Yinyang::set_yao_space(int space)
{
my_yao_space = space;
}
void Yinyang::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
/* 1. background */
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::gray);
painter.drawRect(rect());
/* 将坐标原点移到窗口中间位置 */
painter.translate(width()>>1, height()>>1);
/* 2. the Eight Diagrams */
draw_the_eight_diagrams(painter);
/* 3. Taiji */
draw_tai_ji(painter);
}
/**
* @brief Yinyang::draw_yin_yang_yao
* 绘制爻
* @param painter
* @param p1 爻起点
* @param p2 爻终点
* @param yao 阴爻/阳爻
* @remark 若yao为YIN_YAO,则p1,p2分别为阴爻前半部分的起点与终点
*/
void Yinyang::draw_yin_yang_yao(QPainter &painter, const QPointF &p1,
const QPointF &p2, Yinyang::EC_YAO yao)
{
painter.save();
painter.setPen(QPen(Qt::black, my_yao_height, Qt::SolidLine, Qt::FlatCap)); // do not cover the end of the line
painter.drawLine(p1, p2);
if (EC_YAO::YIN_YAO == yao) {
qreal yin_yao_len = my_yang_yao_len*3/7.0;
qreal space = my_yang_yao_len/7.0;
QPointF pstart(p1), pstop(p2);
pstart.setX(p2.x() + space);
pstop.setX(p2.x() + yin_yao_len + space);
painter.drawLine(pstart, pstop);
}
painter.restore();
}
/**
* @brief Yinyang::draw_diagram
* 绘制卦
* @param painter
* @param diagram 需要绘制的卦
* @remark 根据需要绘制的卦,分别设置该卦的三个爻的起点,终点,属性,
* 依据设置的样式,判定该卦的位置,设置相应的旋转角度
*/
void Yinyang::draw_diagram(QPainter &painter, EC_DIAGRAMS diagram)
{
painter.save();
EC_YAO top = EC_YAO::YANG_YAO; /* 三爻 */
EC_YAO middle = EC_YAO::YANG_YAO; /* 二爻 */
EC_YAO bottom = EC_YAO::YANG_YAO; /* 初爻 */
QPointF p01, p02; /* 初爻起点与终点 */
QPointF p11, p12; /* 二爻起点与终点 */
QPointF p21, p22; /* 三爻起点与终点 */
qint16 x(my_yang_yao_len>>1), y(my_yao_height + my_yao_space);
qint16 twice_y(y<<1);
qint16 triple_y(twice_y + y);
qreal yin_yao_len = my_yang_yao_len*3/7.0; /* 设置阴爻间空隙为1/7阳爻长度 */
qreal yin_yao_stop = x - yin_yao_len;
/* 基于先天八卦的乾位旋转 */
if (EC_DIAGRAMS::QIAN == diagram) { // 三连
p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(x, -my_radius - triple_y);// top
p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(x, -my_radius - twice_y); // middle
p01 = QPointF(-x, -my_radius - y), p02 = QPointF(x, -my_radius - y); // bottom
if (EC_STYLE::HOU_TIAN == my_style)
painter.rotate(135);
}
else if (EC_DIAGRAMS::KUN == diagram) { // 三断
p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(-yin_yao_stop, -my_radius - triple_y);
p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(-yin_yao_stop, -my_radius - twice_y);
p01 = QPointF(-x, -my_radius - y), p02 = QPointF(-yin_yao_stop, -my_radius - y);
top = middle = bottom = EC_YAO::YIN_YAO;
if (EC_STYLE::XIAN_TIAN == my_style)
painter.rotate(180);
else if (EC_STYLE::HOU_TIAN == my_style)
painter.rotate(45);
}
else if (EC_DIAGRAMS::DUI == diagram) { // 上缺
p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(-yin_yao_stop, -my_radius - triple_y);
p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(x, -my_radius - twice_y);
p01 = QPointF(-x, -my_radius - y), p02 = QPointF(x, -my_radius - y);
top = EC_YAO::YIN_YAO;
middle = bottom = EC_YAO::YANG_YAO;
if (EC_STYLE::XIAN_TIAN == my_style)
painter.rotate(-45);
else if (EC_STYLE::HOU_TIAN == my_style)
painter.rotate(90);
}
else if (EC_DIAGRAMS::LI == diagram) { // 中虚
p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(x, -my_radius - triple_y);
p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(-yin_yao_stop, -my_radius - twice_y);
p01 = QPointF(-x, -my_radius - y), p02 = QPointF(x, -my_radius - y);
top = bottom = EC_YAO::YANG_YAO;
middle = EC_YAO::YIN_YAO;
if (EC_STYLE::XIAN_TIAN == my_style)
painter.rotate(-90);
}
else if (EC_DIAGRAMS::ZHEN == diagram) { // 仰盂
p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(-yin_yao_stop, -my_radius - triple_y);
p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(-yin_yao_stop, -my_radius - twice_y);
p01 = QPointF(-x, -my_radius - y), p02 = QPointF(x, -my_radius - y);
top = middle = EC_YAO::YIN_YAO;
bottom = EC_YAO::YANG_YAO;
if (EC_STYLE::XIAN_TIAN == my_style)
painter.rotate(-135);
else if (EC_STYLE::HOU_TIAN == my_style)
painter.rotate(-90);
}
else if (EC_DIAGRAMS::XUN == diagram) { // 下断
p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(x, -my_radius - triple_y);
p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(x, -my_radius - twice_y);
p01 = QPointF(-x, -my_radius - y), p02 = QPointF(-yin_yao_stop, -my_radius - y);
top = middle = EC_YAO::YANG_YAO;
bottom = EC_YAO::YIN_YAO;
if (EC_STYLE::XIAN_TIAN == my_style)
painter.rotate(45);
else if (EC_STYLE::HOU_TIAN == my_style)
painter.rotate(-45);
}
else if (EC_DIAGRAMS::KAN == diagram) { // 中满
p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(-yin_yao_stop, -my_radius - triple_y);
p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(x, -my_radius - twice_y);
p01 = QPointF(-x, -my_radius - y), p02 = QPointF(-yin_yao_stop, -my_radius - y);
middle = EC_YAO::YANG_YAO;
top = bottom = EC_YAO::YIN_YAO;
if (EC_STYLE::XIAN_TIAN == my_style)
painter.rotate(90);
else if (EC_STYLE::HOU_TIAN == my_style)
painter.rotate(180);
}
else if (EC_DIAGRAMS::GEN == diagram) { // 覆碗
p21 = QPointF(-x, -my_radius - triple_y), p22 = QPointF(x, -my_radius - triple_y);
p11 = QPointF(-x, -my_radius - twice_y), p12 = QPointF(-yin_yao_stop, -my_radius - twice_y);
p01 = QPointF(-x, -my_radius - y), p02 = QPointF(-yin_yao_stop, -my_radius - y);
top = EC_YAO::YANG_YAO;
middle = bottom = EC_YAO::YIN_YAO;
if (EC_STYLE::XIAN_TIAN == my_style)
painter.rotate(135);
else if (EC_STYLE::HOU_TIAN == my_style)
painter.rotate(-135);
}
else {
painter.restore();
return;
}
draw_yin_yang_yao(painter, p21, p22, top);
draw_yin_yang_yao(painter, p11, p12, middle);
draw_yin_yang_yao(painter, p01, p02, bottom);
painter.restore();
}
/* 绘制八卦 */
void Yinyang::draw_the_eight_diagrams(QPainter &painter)
{
painter.save();
draw_diagram(painter, EC_DIAGRAMS::QIAN);
draw_diagram(painter, EC_DIAGRAMS::KUN);
draw_diagram(painter, EC_DIAGRAMS::LI);
draw_diagram(painter, EC_DIAGRAMS::KAN);
draw_diagram(painter, EC_DIAGRAMS::DUI);
draw_diagram(painter, EC_DIAGRAMS::XUN);
draw_diagram(painter, EC_DIAGRAMS::ZHEN);
draw_diagram(painter, EC_DIAGRAMS::GEN);
painter.restore();
}
/* 绘制太极图 */
void Yinyang::draw_tai_ji(QPainter &painter)
{
painter.save();
/* 旋转坐标系统 */
painter.rotate(my_angle);
/* 白鱼 */
QPainterPath circle, tmp;
circle.arcTo(-my_radius, -my_radius, my_radius<<1, my_radius<<1, 270, 180);
tmp.addEllipse(QPointF(0, -my_radius>>1), my_radius>>1, my_radius>>1);
circle -= tmp; /* 上半部减去半个圆 */
tmp.clear();
tmp.addEllipse(QPointF(0, my_radius>>1), my_radius>>1, my_radius>>1);
circle += tmp; /* 下半部加上半个圆 */
painter.fillPath(circle, Qt::white);
tmp.clear();
tmp.addEllipse(QPointF(0, my_radius>>1), my_radius>>2, my_radius>>2);
painter.fillPath(tmp, Qt::black); /* 画上鱼眼 */
/* 黑鱼 */
circle.clear();
circle.arcTo(-my_radius, -my_radius, my_radius<<1, my_radius<<1, 90, 180);
tmp.clear();
tmp.addEllipse(QPointF(0, -my_radius>>1), my_radius>>1, my_radius>>1);
circle += tmp; /* 上半部加上半个圆 */
tmp.clear();
tmp.addEllipse(QPointF(0, my_radius>>1), my_radius>>1, my_radius>>1);
circle -= tmp; /* 下半部减去半个圆 */
painter.fillPath(circle, Qt::black);
tmp.clear();
tmp.addEllipse(QPointF(0, -my_radius>>1), my_radius>>2, my_radius>>2);
painter.fillPath(tmp, Qt::white); /* 画上黑鱼的大白眼 */
painter.restore();
}
testtaiji.h
#ifndef TESTTAIJI_H
#define TESTTAIJI_H
#include
#include
#include "yinyang.h"
class TestTaiji : public QWidget
{
Q_OBJECT
public:
explicit TestTaiji(QWidget *parent = nullptr);
virtual ~TestTaiji();
signals:
private:
QGridLayout * my_gridlayout;
Yinyang * my_yinyang_xian_tian;
Yinyang * my_yinyang_hou_tian;
};
#endif // TESTTAIJI_H
#include "testtaiji.h"
TestTaiji::TestTaiji(QWidget *parent) : QWidget(parent)
{
my_yinyang_xian_tian = new Yinyang;
my_yinyang_hou_tian = new Yinyang;
my_yinyang_hou_tian->set_style(Yinyang::EC_STYLE::HOU_TIAN);
my_gridlayout = new QGridLayout;
my_gridlayout->addWidget(my_yinyang_xian_tian, 0, 0);
my_gridlayout->addWidget(my_yinyang_hou_tian, 0, 1);
setLayout(my_gridlayout);
}
TestTaiji::~TestTaiji()
{
if (my_yinyang_xian_tian) {
delete my_yinyang_xian_tian;
my_yinyang_xian_tian = nullptr;
}
if (my_yinyang_hou_tian) {
delete my_yinyang_hou_tian;
my_yinyang_hou_tian = nullptr;
}
if (my_gridlayout) {
delete my_gridlayout;
my_gridlayout = nullptr;
}
}
#include
#include "testtaiji.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TestTaiji ttj;
ttj.show();
return a.exec();
}