#ifndef MYCHART_H
#define MYCHART_H
#include <QWidget>
#include <QPainter>
#include <QString>
struct DataNode
{
int value;
QString key;
};
// MyChart继承自QWidget类,是一个窗口小部件。
class MyChart : public QWidget
{
Q_OBJECT
public:
// `explicit` 是 C++ 中的一个关键字,用于修饰类的构造函数,表示该构造函数只能用于显式地创建对象,不能被隐式地调用。
// 只能通过MyChart painter = MyChart(parent)的方式显式地创建一个 `MyChart` 对象:
// `parent` 参数的默认值为 `nullptr`,这表示如果没有提供父部件的指针,那么 `MyChart` 就没有父部件,即它是一个独立的窗口部件。
explicit MyChart(QWidget *parent = nullptr);
~MyChart();
void updateValue(const DataNode &node);
// 创建坐标轴
void AxisCreate();
void AxisCurvesUpdate(const QList<QPoint> & pointList);
protected:
void paintEvent(QPaintEvent *event);
private:
QPainter *m_painter;
QPoint m_pointZero;
QPoint m_pointEndX;
QPoint m_pointEndY;
QPoint m_pointEndA;
//坐标轴其实结束点
int m_axisStartX;
int m_axisStartY;
int m_axisEndX;
int m_axisEndY;
// 高度
int m_lenY;
// 长度
int m_lenX;
// 每个刻度之间间隔的长度
int m_PeriodX;
int m_PeriodY;
// 刻度间隔数
int m_durationX = 100;
int m_durationY = 10;
int yMaxValue = 10;
int maxNodeNum = 110;
QList<DataNode> listDataNode;
};
#endif // MYCHART_H
#include "mychart.h"
MyChart::MyChart(QWidget *parent) : QWidget(parent)
{
m_painter = new QPainter(this);
}
MyChart::~MyChart()
{
delete m_painter;
}
// 数据刷新
void MyChart::updateValue(const DataNode &node)
{
// 如果当前列表中的数据节点数量已经达到了最大值 `maxNodeNum`,
// 先删除队列头部的元素,即最早加入的元素(使用 `removeFirst()` 函数)。
if(listDataNode.size() >= maxNodeNum) {
listDataNode.removeFirst();
}
// 然后,将数据节点 `node` 添加到当前列表的末尾,使用 `append()` 函数实现。
listDataNode.append(node);
// 最后,将整个图表更新,调用 `update()` 函数。
// `update()` 函数是用来触发 `paintEvent()` 函数的信号的。
// 当窗口或控件需要更新或重绘自己时,它们会同时发射一个 `update()` 信号。
// 这个信号会被 Qt 的事件循环机制捕获,最终调用 `paintEvent()` 函数进行绘图。
// 因此,如果不调用 `update()` 函数,`paintEvent()` 函数就不会被调用,也就不会更新图表的显示内容。
update();
}
// 创建坐标轴
void MyChart::AxisCreate()
{
m_painter->setRenderHint(QPainter::Antialiasing);
QPen pen;
pen.setWidth(2);
pen.setColor(QColor(100, 200, 100));
m_painter->setPen(pen);
m_axisStartX = 30;
m_axisEndX = this->width() - 30;
m_axisStartY = this->height() - 30;
m_axisEndY = 30;
// 坐标轴高度
m_lenX = m_axisEndX - m_axisStartX + 1;
// 坐标轴长度
m_lenY = m_axisStartY - m_axisEndY + 1;
// 坐标端点设置
m_pointZero = QPoint(m_axisStartX, m_axisStartY);
m_pointEndX = QPoint(m_axisEndX, m_axisStartY);
m_pointEndY = QPoint(m_axisStartX, m_axisEndY);
m_pointEndA = QPoint(m_axisEndX, m_axisEndY);
// x轴线
m_painter->drawLine(m_pointZero, m_pointEndX);
// 左侧y轴线
m_painter->drawLine(m_pointZero, m_pointEndY);
// 右侧y轴线
m_painter->drawLine(m_pointEndX, m_pointEndA);
// 绘制刻度
// 每个刻度之间间隔的长度
m_PeriodX = m_lenX / m_durationX - 1;
m_PeriodY = m_lenY / m_durationY - 1;
// 绘制坐标轴上的刻度和数字,用以标示坐标轴上每个刻度对应的数值
for (int i = 0; i < m_durationX; ++i) {
// 绘制表示x轴刻度的垂直线段
m_painter->drawLine(QPoint(m_pointZero.x() + i * m_PeriodX, m_pointZero.y() + 5), QPoint(m_pointZero.x() + i * m_PeriodX, m_pointZero.y() - 5));
}
// 左侧y轴刻度
for (int i = 0; i <= m_durationY; ++i) {
// 绘制表示y轴刻度的水平线段
m_painter->drawLine(QPoint(m_pointZero.x() - 5, m_pointZero.y() - i * m_PeriodY), QPoint(m_pointZero.x() + 5, m_pointZero.y() - i * m_PeriodY));
QString value = QString::number(i * 2);
// 绘制刻度数值
m_painter->drawText(QPoint(m_pointZero.x() - 25, m_pointZero.y() - i * m_PeriodY + 5), value);
}
// 右侧y轴刻度
for (int i = 0; i <= m_durationY; ++i) {
// 绘制表示y轴刻度的水平线段
m_painter->drawLine(QPoint(m_pointEndX.x() - 1, m_pointEndX.y() - i * m_PeriodY), QPoint(m_pointEndX.x() + 5, m_pointEndX.y() - i * m_PeriodY));
QString value = QString::number(i * 2);
// 绘制刻度数值
m_painter->drawText(QPoint(m_pointEndX.x() - 25, m_pointEndX.y() - i * m_PeriodY + 5), value);
}
}
void MyChart::AxisCurvesUpdate(const QList<QPoint> & pointList)
{
QPen pen;
// 折线线条宽度
pen.setWidth(3);
// 折现线条颜色
pen.setColor(Qt::green);
m_painter->setPen(pen);
// 遍历并连接个数据节点,绘制折线
for(int i = 0; i < pointList.size(); i++) {
if((i+1) < pointList.size()) {
// 连接个数据点,绘制折线
m_painter->drawLine(pointList.at(i), pointList.at(i+1));
}
}
}
void MyChart::paintEvent(QPaintEvent *event)
{
(void)event;
m_painter->begin(this);
// 创建坐标轴
AxisCreate();
#if 1
// 更新数据
QList<QPoint> pointList;
QList<QPoint> pointList2;
for(int i = 0; i < listDataNode.size(); i++) {
DataNode node = listDataNode.at(i);
QString key = node.key;
int value = node.value;
// 当前数据在x轴位置对应的刻度值
int xOffset = m_pointZero.x() + i * m_PeriodX;
// 当前数据在y轴位置对应的刻度值
int yOffset = value * m_lenY / yMaxValue;
// 像数据列表中添加数据转换后对应的坐标点
pointList << QPoint(xOffset, m_pointZero.y() - yOffset);
pointList2 << QPoint(xOffset - 25, m_pointZero.y() - yOffset - 25);
// 使用 `QTransform` 类对绘制坐标文本的位置和方向进行变换
QTransform transform;
transform.translate(xOffset + 5, m_pointZero.y() - 7);
transform.rotate(-45);
m_painter->setTransform(transform);
m_painter->drawText(0, 5, key);
m_painter->resetTransform();
}
QPen pen;
// 折线线条宽度
pen.setWidth(3);
// 折现线条颜色
pen.setColor(Qt::red);
m_painter->setPen(pen);
// 遍历并连接个数据节点,绘制折线
for(int i = 0; i < pointList.size(); i++) {
if((i+1) < pointList.size()) {
// 连接个数据点,绘制折线
m_painter->drawLine(pointList.at(i), pointList.at(i+1));
}
}
AxisCurvesUpdate(pointList2);
#endif
m_painter->end();
}