这几天自己绘制了一些小部件,主要就是重写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 <QWidget> 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_Hyinyang.cpp
#include <QPaintEvent> #include <QPainter> #include <QTimer> #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 <QWidget> #include <QGridLayout> #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