参考
参考
mychart.h
#ifndef MYCHART_H
#define MYCHART_H
#include <QWidget>
#include <QPainter>
#include <QString>
struct DataNode
{
int value;
QString key;
};
typedef struct {
QString label;
double width;
QColor color;
} CvsInfo;
// 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 AxisCurvesUpdate(const QList<QPoint> & pointList);
// 坐标轴参数初始化设置
typedef struct {
QString titleX;
quint32 durationX;
QString titleYl;
quint32 durationYl;
QString titleYr;
quint32 dudrationYr;
} AxisInfo;
// 坐标参数初始化设置
typedef struct {
QPointF spMin;
QPointF spMax;
QVector<QPointF> sp;
QPointF epMin;
QPointF epMax;
QVector<QPointF> ep;
} MyGtChartAxisYScalesInfo;
// 坐标轴初始化设置
void AxisInit(const AxisInfo & info);
// 曲线参数初始化设置
void AxisCurvesAppend(const QString & label, double width, QColor color);
// 曲线标题显示与设置
void AxisCurvesTitleSet();
// 坐标轴刻度线设置
void AxisScalesLineSet();
// 坐标轴刻度值刷新
void AxisScalesValueSet();
// 坐标轴标题设置
void MyChartTitileSet(QPainter & painter, const QPoint &point, const QString &title, const QColor & color, const qreal & fontSize);
// 坐标轴标题设置
void AxisTitleSet();
// 坐标轴初始化设置
void AxisInitSet();
// 创建坐标轴
void AxisCreateTest();
// 曲线坐标点刷新
void AxisPointsUpdate(QVector<QList<QPoint>> &pointList);
// 平滑曲线绘制
void SmoothCurvesDrawTest(const QColor &color, qreal &width, const QList<QPoint> &pointList);
// 左侧y轴弧线绘制,刻度点记录
void AxisYlArcDrawWidthScales(QPainter &painter, int offsetX, int diameters, int tickCount, int margin);
// 右侧y轴弧线绘制,刻度点记录
void AxisYrArcDrawWidthScales(QPainter &painter, int offsetX, int diameters, int tickCount, int margin);
// 曲线图刷新
void ChartViewUpdate();
void drawArcAtWidget(QPainter * painter, const QPoint& center, int radius);
// 右侧码表
//绘制实时时速数字
void DrawDbNum(QPainter& painter, QPoint & point);
// 绘制一条中间略粗两边略尖的渐变发光线条
void DrawDbLightLine(QPainter& painter, QPoint & point);
// 中间小圆
void DrawCircle_bom_small(QPainter& painter, QPoint & center, int radius);
//渐变发光内圈
void DrawCircle_bom_shine(QPainter &painter, QPoint ¢er, int radius);
// 最外细圆线
void DrawCircle_line(QPainter& painter, QPoint ¢er, int radius, int width);
// 绘制指针
void DrawPointer(QPainter& painter, QPoint ¢er, int radius, int degRotate);
// 动态扇形环
void DrawCircle_arc(QPainter& painter, QPoint ¢er, int radius, int degRotate);
// 渐变发光外扇形
void DrawCircle(QPainter& painter, QPoint ¢er, int radius);
// 刻度数字
void DrawDigital(QPainter& painter, QPoint ¢er, int radius);
// 绘制刻度
void DrawSmallScale(QPainter& painter, QPoint ¢er, int radius);
//绘制码表盘
void DrawDb(QPainter& painter, QPoint & center);
void DashBoardUpdate(const QPoint & point);
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; // 长度
double m_periodX; // x轴每个刻度之间间隔的长度
double m_periodY; // y轴每个刻度之间间隔的长度
quint32 m_marginX = 300; // y轴距离左右边界宽度
quint32 m_marginY = 25; // x轴距离上下边界宽度
int m_durationX = 100; // x刻度间隔数
int m_durationY = 10; // y刻度间隔数
QVector<double> m_valYMax;
int m_nodeNumMax;
QList<CvsInfo> m_cvsInfo;
AxisInfo m_axisInfo;
bool m_isAxisReady = false;
QList<DataNode> m_listDataNode;
MyGtChartAxisYScalesInfo m_scalesY;
quint32 m_marginY2Dash = 0; // 左右y轴到马表盘的间隔
// 马表盘
int m_maxValueDashBoard = 50;
int m_minValueDashBoard = 0;
int m_curValueDashBoard = 20;
// painter移动位置
QPoint m_pleftG;
QPoint m_prightT;
QPoint m_pAxis;
};
#endif // MYCHART_H
mychart.cpp
#include "mychart.h"
#include <QDebug>
#include <QtMath>
MyChart::MyChart(QWidget *parent) : QWidget(parent)
{
m_painter = new QPainter(this);
}
MyChart::~MyChart()
{
delete m_painter;
}
// 数据刷新
void MyChart::updateValue(const DataNode &node)
{
if(m_listDataNode.size() >= m_nodeNumMax) {
m_listDataNode.removeFirst();
}
m_listDataNode.append(node);
update();
}
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::AxisInit(const MyChart::AxisInfo &info)
{
m_axisInfo = info;
m_durationX = info.durationX;
m_nodeNumMax = m_durationX + 1;
m_durationY = info.durationYl;
// 初始化设置
m_valYMax.append(10);
m_valYMax.append(10);
m_isAxisReady = true;
}
// 曲线参数初始化设置
void MyChart::AxisCurvesAppend(const QString &label, double width, QColor color)
{
CvsInfo info;
info.label = label;
info.width = width;
info.color = color;
m_cvsInfo.append(info);
}
// 曲线标题显示与设置
void MyChart::AxisCurvesTitleSet()
{
QVector<double> strlen; // 每个标题长度
double lenSum = 0; // 所有标题总长度
double linelen = 40; // 标题前面线条长度
double spacelen = 30.0; // 标题之间间隔长度
double fontSize = 10.0; // 字体大小
// 计算标题占用的总长度
for (int i = 0; i < m_cvsInfo.size(); ++i) {
m_painter->setFont(font());
QFontMetrics fm(m_painter->font());
double len = (double)fm.width(m_cvsInfo[i].label + spacelen + linelen);
strlen.append(len);
lenSum += len;
}
// 起始偏移长度
double startLen = (m_lenX - lenSum) / 2;
for (int i = 0; i < m_cvsInfo.size(); ++i) {
QPen pen(m_cvsInfo[i].color);
pen.setWidthF(m_cvsInfo[i].width);
m_painter->setPen(pen);
m_painter->drawLine(QPoint(m_scalesY.sp.last().x() + startLen, m_scalesY.sp.last().y() / 2),
QPoint(m_scalesY.sp.last().x() + startLen + linelen, m_scalesY.sp.last().y() / 2));
m_painter->setPen(Qt::NoPen);
startLen += linelen; // 偏移已经占用的长度
QFont font("Century", fontSize, QFont::Bold);
font.setLetterSpacing(QFont::AbsoluteSpacing, 0.1);
m_painter->setFont(font);
pen.setColor(Qt::black);
m_painter->setPen(pen);
m_painter->drawText(QPoint(m_scalesY.sp.last().x() + startLen, m_scalesY.sp.last().y() - fontSize / 2), m_cvsInfo[i].label);
startLen += strlen[i]; // 偏移已经占用的长度
}
}
// 坐标轴刻度线设置
void MyChart::AxisScalesLineSet()
{
QPen pen;
// 轴线刻度绘制
m_periodX = (double) (m_lenX * 1.0) / (m_durationX); // x刻度间隔长度
m_periodY = m_lenY / (m_durationY - 1); // y刻度间隔长度
// x刻度线绘制
pen.setWidthF(0.5);
pen.setColor(Qt::blue);
m_painter->setPen(pen);
for (int i = 0; i < m_durationX; ++i) {
m_painter->drawLine(QPointF((double)(m_scalesY.spMax.x() + i * m_periodX) + 80, m_scalesY.sp.first().y() + 5),
QPointF((double)(m_scalesY.spMax.x() + i * m_periodX) + 80, m_scalesY.sp.first().y() - 5));
for (int j = 0; j < m_scalesY.sp.size(); ++j) {
}
}
}
// 坐标轴刻度值刷新
void MyChart::AxisScalesValueSet()
{
QPen pen;
pen.setWidthF(0.2);
pen.setColor(Qt::black);
m_painter->setPen(pen);
QFont font;
font.setPointSizeF(10);
m_painter->setFont(font);
// 刷新左侧y轴刻度值
for (int i = 0; i < m_durationY; ++i) {
double lenY = m_valYMax[0];
QString value = QString::number(i * (lenY / m_durationY), 'f', 2);
QFontMetrics f(m_painter->font());
int w = f.width(value);
m_painter->setPen(QPen(Qt::red, 1.1));
m_painter->drawLine(m_scalesY.sp[i], QPoint(m_scalesY.sp[i].x() + w + 5, m_scalesY.sp[i].y()));
m_painter->setPen(Qt::NoPen);
m_painter->setPen(pen);
if (m_scalesY.sp[i].y() > m_scalesY.spMax.y()) {
if (i == 0) {
m_painter->drawText(QPoint(m_scalesY.sp[i].x() + 6, m_scalesY.sp[i].y()), value);
} else if (i == 1) {
m_painter->drawText(QPoint(m_scalesY.sp[i].x() + 4, m_scalesY.sp[i].y()), value);
} else if (i == 2) {
m_painter->drawText(QPoint(m_scalesY.sp[i].x() + 3, m_scalesY.sp[i].y()), value);
} else {
m_painter->drawText(QPoint(m_scalesY.sp[i].x() + m_scalesY.sp.size() - 2 * i, m_scalesY.sp[i].y()), value);
}
} else {
m_painter->drawText(QPoint(m_scalesY.sp[i].x() + 1, m_scalesY.sp[i].y()), value);
}
}
// 刷新右侧y轴刻度值
for (int i = 0; i < m_durationY; ++i) {
double lenY = m_valYMax[1];
QString value = QString::number(i * (lenY / m_durationY), 'f', 2);
QFontMetrics f(m_painter->font());
int w = f.width(value);
m_painter->setPen(QPen(Qt::red, 1.1));
m_painter->drawLine(m_scalesY.ep[i], QPoint(m_scalesY.ep[i].x() - w - 5, m_scalesY.ep[i].y()));
m_painter->setPen(Qt::NoPen);
m_painter->setPen(pen);
if (m_scalesY.ep[i].y() > m_scalesY.epMax.y()) {
if (i == 0) {
m_painter->drawText(QPoint(m_scalesY.ep[i].x() - w - 6, m_scalesY.ep[i].y()), value);
} else if (i == 1) {
m_painter->drawText(QPoint(m_scalesY.ep[i].x() - w - 4, m_scalesY.ep[i].y()), value);
} else if (i == 2) {
m_painter->drawText(QPoint(m_scalesY.ep[i].x() - w - 3, m_scalesY.ep[i].y()), value);
} else {
m_painter->drawText(QPoint(m_scalesY.ep[i].x() - w - 2, m_scalesY.ep[i].y()), value);
}
} else {
m_painter->drawText(QPoint(m_scalesY.ep[i].x() - w - 1, m_scalesY.ep[i].y()), value);
}
}
// 刷新x轴刻度值
for (int i = 0; i < m_durationX; ++i) {
if (i >= m_listDataNode.size()) {
break;
}
QTransform transform;
transform.translate(m_pointZero.x() + i * m_periodX + 2, m_pointZero.y() - 5);
transform.rotate(-45);
m_painter->setTransform(transform);
m_painter->drawText(0, 3, m_listDataNode[i].key);
m_painter->resetTransform();
}
}
// 坐标轴标题设置
void MyChart::MyChartTitileSet(QPainter & painter, const QPoint &point, const QString &title, const QColor & color, const qreal & fontSize)
{
painter.save();
painter.setPen(QPen(color, fontSize));
QFont font("Century", fontSize, QFont::Bold);
font.setPointSizeF(fontSize);
painter.setFont(font);
painter.drawText(point, title);
painter.restore();
}
// 坐标轴标题设置
void MyChart::AxisTitleSet()
{
// x轴标题设置
MyChartTitileSet(*m_painter,
QPoint((m_scalesY.ep.first().x() - m_scalesY.sp.first().x()) / 2 + m_scalesY.sp.first().x(), m_scalesY.sp.first().y() + 15),
m_axisInfo.titleX,
Qt::red,
12.0);
// 左侧y轴
MyChartTitileSet(*m_painter,
QPoint(m_scalesY.sp.last().x() + 50, m_scalesY.sp.last().y()),
m_axisInfo.titleYl,
Qt::black,
10.0);
// 右侧y轴
MyChartTitileSet(*m_painter,
QPoint(m_scalesY.ep.last().x() - 180, m_scalesY.ep.last().y()),
m_axisInfo.titleYr,
Qt::black,
10.0);
}
// 坐标轴初始化设置
void MyChart::AxisInitSet()
{
QPen pen;
pen.setWidthF(0.5);
pen.setColor(Qt::gray);
m_painter->setPen(pen);
double maxValue = m_valYMax[0];
QString strMax = QString::number(m_durationY * (maxValue / m_durationY), 'f', 2);
QFontMetrics f(m_painter->font());
int xMargin = f.width(strMax);
m_axisStartX = xMargin - 3 + qMin(width(), height());
maxValue = m_valYMax[1];
strMax = QString::number(m_durationY * (maxValue / m_durationY), 'f', 2);
xMargin = f.width(strMax);
m_axisEndX = this->width() - xMargin - qMin(width(), height());
m_axisStartY = this->height() - m_marginY;
m_axisEndY = m_marginY;
m_lenX = m_axisEndX - m_axisStartX;
m_lenY = m_axisStartY - m_axisEndY;
// 坐标点设置
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);
// 左侧y轴弧线绘制
int offsetXl = qMin(width(), height()) + m_marginY2Dash;
int diameters = qMin(width(), height()) + qMin(width(), height()) / 2;
AxisYlArcDrawWidthScales(*m_painter, offsetXl, diameters, 10, m_marginY);
// 右侧y轴弧线绘制,刻度点记录
int diametersR = qMin(width(), height()) + qMin(width(), height()) / 2;
int offsetXr = width() - qMin(width(), height()) - m_marginY2Dash;
AxisYrArcDrawWidthScales(*m_painter, offsetXr, diametersR, 10, m_marginY);
// 底侧x轴线与y轴刻度等分线绘制
QPen level; // 等分线
for (int i = 0; i < m_scalesY.sp.size(); ++i) {
m_painter->setPen(Qt::NoPen);
if (i == 0) {
level.setColor(Qt::cyan);
level.setWidthF(0.5);
} else if (i % 2) {
level.setColor(Qt::lightGray);
level.setWidthF(0.3);
} else {
level.setColor(Qt::gray);
level.setWidthF(0.3);
}
m_painter->setPen(level);
m_painter->drawLine(m_scalesY.sp[i], m_scalesY.ep[i]);
}
// 坐标轴刻度线设置
AxisScalesLineSet();
}
// 创建坐标轴
void MyChart::AxisCreateTest()
{
m_painter->setRenderHint(QPainter::Antialiasing);
// 坐标轴初始化设置
AxisInitSet();
// 坐标轴刻度值刷新
AxisScalesValueSet();
// 坐标轴标题设置
AxisTitleSet();
// 曲线标题显示与设置
AxisCurvesTitleSet();
}
// 曲线坐标点刷新
void MyChart::AxisPointsUpdate(QVector<QList<QPoint>> &pointList)
{
quint16 level = 15;
for (int i = 0; i < m_listDataNode.size(); ++i) {
DataNode node = m_listDataNode.at(i);
for (int j = 0; j < 1; ++j) {
if (j >=m_valYMax.size()) {
m_valYMax.resize(j + 1);
}
if (m_valYMax[j] < node.value + node.value / 4) {
m_valYMax[j] = node.value + node.value / 4 + j * level;
}
}
}
// 刷新曲线坐标点
for (int i = 0; i < m_listDataNode.size(); ++i) {
DataNode node = m_listDataNode.at(i);
quint8 num = 1;
if (pointList.size() < num) {
pointList.resize(num);
}
int offsetX = m_pointZero.x() + i * m_periodX;
for (int j = 0; j < num; ++j) {
if (j >= m_valYMax.size()) {
return;
}
double offsetY = (double)((node.value / m_valYMax[j]) * m_lenY);
pointList[j] << QPoint(offsetX, m_pointZero.y() - offsetY);
}
}
}
// 平滑曲线绘制
void MyChart::SmoothCurvesDrawTest(const QColor &color, qreal &width, const QList<QPoint> &pointList)
{
QPainterPath path(pointList[0]);
for (int i = 0; i < pointList.size() - 1; ++i) {
QPointF sp = pointList[i];
QPointF ep = pointList[i + 1];
QPointF c1 = QPointF((sp.x() + ep.x()) / 2, sp.y());
QPointF c2 = QPointF((sp.x() + ep.x()) / 2, ep.y());
path.cubicTo(c1, c2, ep);
}
m_painter->setRenderHints(QPainter::Antialiasing, true);
m_painter->setPen(QPen(color, width));
m_painter->drawPath(path);
}
// 左侧y轴弧线绘制,刻度点记录
// offsetX: 弧线最右侧举例widget最左侧的长度
// diameters: 圆弧直径,即外切举行的边长
// tickCount : 刻度个数
// margin : y轴最上边刻度与最下边刻度距离widget上下边界的距离
void MyChart::AxisYlArcDrawWidthScales(QPainter &painter, int offsetX, int diameters, int tickCount, int margin)
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
painter.resetTransform();
painter.setPen(QPen(QColor(0, 85, 127), 3.0));
// 当前widget内包含最大正方形的边长
int rectLen = qMin(width(), height());
// 外切矩形的起始位置
int rectStartX = offsetX - diameters;
// 弧线实际外接矩形的y方向起始位置
int rectStartY = (rectLen - diameters) / 2;
QPoint rectStartPoint = QPoint(rectStartX, rectStartY);
// 弧线实际外切矩形边长设置
QSize size = QSize(diameters, diameters);
// 弧线实际外切矩形
QRect rect(rectStartPoint, size);
// 半径长度,对应三角函数中斜边长度
int radius = diameters / 2;
qreal opSideLen = ((qreal)((rectLen > diameters) ? diameters : rectLen)) / 2;
// 起始角度
qreal startAngle = -qRadiansToDegrees(qAsin(opSideLen / radius));
// 旋转角度
qreal spanAngle = 2 * startAngle;
// 绘制弧线
painter.drawArc(rect, (int)(startAngle * 16), -(int)(spanAngle * 16));
// 刻度点记录
opSideLen -= margin;
startAngle = -qRadiansToDegrees(qAsin(opSideLen / radius));
spanAngle = 2 * startAngle;
// 刻度点之间的角度间隔
qreal intervalAngle = spanAngle / (tickCount - 1);
m_scalesY.sp.clear();
int i = 0;
// 当前刻度对应的角度数
qreal angle = startAngle - i * intervalAngle;
// 刻度圆点的x坐标
qreal x = rectStartX + radius + radius * qCos(qDegreesToRadians(angle));
// 刻度点的y轴坐标
qreal y = rectStartY + radius - radius * qSin(qDegreesToRadians(angle));
m_scalesY.spMin = QPoint(x, y);
m_scalesY.sp.append(QPoint(x, y));
painter.setPen(QPen(Qt::red, 5));
painter.drawPoint(x, y);
for (i = 1; i < tickCount; ++i) {
angle = startAngle - i * intervalAngle;
x = rectStartX + radius + radius * qCos(qDegreesToRadians(angle));
y = rectStartY + radius - radius * qSin(qDegreesToRadians(angle));
m_scalesY.sp.append(QPoint(x, y));
painter.drawPoint(x, y);
if (m_scalesY.spMin.x() > x) {
m_scalesY.spMin = QPoint(x, y);
}
if (m_scalesY.spMax.x() < x) {
m_scalesY.spMax = QPoint(x, y);
}
}
painter.restore();
}
// 右侧y轴弧线绘制,刻度点记录
// offsetX: 弧线最右侧举例widget最左侧的长度
// diameters: 圆弧直径,即外切举行的边长
// tickCount : 刻度个数
// margin : y轴最上边刻度与最下边刻度距离widget上下边界的距离
void MyChart::AxisYrArcDrawWidthScales(QPainter &painter, int offsetX, int diameters, int tickCount, int margin)
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
painter.resetTransform();
painter.setPen(QPen(QColor(0, 85, 127), 3.0));
// 当前widget内包含最大正方形的边长
int rectLen = qMin(width(), height());
// 外切矩形的起始位置
int rectStartX = offsetX;
// 弧线实际外接矩形的y方向起始位置
int rectStartY = (rectLen - diameters) / 2;
QPoint rectStartPoint = QPoint(rectStartX, rectStartY);
// 弧线实际外切矩形边长设置
QSize size = QSize(diameters, diameters);
// 弧线实际外切矩形
QRect rect(rectStartPoint, size);
// 半径长度,对应三角函数中斜边长度
int radius = diameters / 2;
qreal opSideLen = ((qreal)((rectLen > diameters) ? diameters : rectLen)) / 2;
// 起始角度
qreal startAngle = qRadiansToDegrees(qAsin(opSideLen / radius));
// 旋转角度
qreal spanAngle = 2 * startAngle;
// 起始角度从widget下边界开始
startAngle = 180 + startAngle;
// 绘制弧线
painter.drawArc(rect, (int)(startAngle * 16), -(int)(spanAngle * 16));
// 刻度点记录
opSideLen -= margin;
startAngle = qRadiansToDegrees(qAsin(opSideLen / radius));
spanAngle = 2 * startAngle;
startAngle += 180;
// 刻度点之间的角度间隔
qreal intervalAngle = spanAngle / (tickCount - 1);
m_scalesY.ep.clear();
int i = 0;
// 当前刻度对应的角度数
qreal angle = startAngle + i * intervalAngle;
// 刻度圆点的x坐标
qreal x = rectStartX + radius + radius * qCos(qDegreesToRadians(angle));
// 刻度点的y轴坐标
qreal y = rectStartY + radius - radius * qSin(qDegreesToRadians(angle));
m_scalesY.epMin = QPoint(x, y);
m_scalesY.ep.append(QPoint(x, y));
painter.setPen(QPen(Qt::red, 5));
painter.drawPoint(x, y);
for (i = 1; i < tickCount; ++i) {
angle = startAngle - i * intervalAngle;
x = rectStartX + radius + radius * qCos(qDegreesToRadians(angle));
y = rectStartY + radius - radius * qSin(qDegreesToRadians(angle));
m_scalesY.ep.append(QPoint(x, y));
painter.drawPoint(x, y);
if (m_scalesY.epMin.x() > x) {
m_scalesY.epMin = QPoint(x, y);
}
if (m_scalesY.epMax.x() < x) {
m_scalesY.epMax = QPoint(x, y);
}
}
painter.restore();
}
// 曲线图刷新
void MyChart::ChartViewUpdate()
{
static int speed = 0;
m_curValueDashBoard = speed++;
QVector<QList<QPoint>> pointList;
// 曲线坐标点刷新
AxisPointsUpdate(pointList);
if (m_valYMax.size() >= 2) {
// 创建坐标轴
AxisCreateTest();
}
// 坐标轴标题设置
AxisTitleSet();
for (int i = 0; i < pointList.size(); ++i) {
if (i >= m_cvsInfo.size()) {
break;
}
SmoothCurvesDrawTest(m_cvsInfo[i].color, m_cvsInfo[i].width, pointList[i]);
}
speed %= 239;
}
//绘制实时数字
void MyChart::DrawDbNum(QPainter& painter, QPoint & point)
{
painter.save();
painter.setPen(QColor(0,255,255));
QFont font;
font.setFamily("Arial");
font.setPointSize(8);
painter.setFont(font);
// 根据 QFont 对象创建 QFontMetrics 实例
QFontMetrics font_metrics(painter.font());
QString str = QString::number(m_curValueDashBoard) + QString("kb/s");
// 获取文本的长度
int text_width = font_metrics.width(str);
// 获取文本的高度
int text_height = font_metrics.height();
QPoint curPoint = QPoint(point.x() - text_width / 2, point.y() - text_height);
painter.drawText(curPoint, str);
str = "数据传输速率";
text_width = font_metrics.width(str);
QPoint curPoint2 = QPoint(point.x() - text_width / 2, point.y() + text_height);
painter.drawText(curPoint2, str);
painter.restore();
}
// 绘制一条中间略粗两边略尖的渐变发光线条
void MyChart::DrawDbLightLine(QPainter& painter, QPoint & point)
{
painter.save();
// 创建白色的画笔,设置较粗的线条和略圆形的线帽
QPen pen(QColor(255, 255, 255));
pen.setWidth(6);
pen.setCapStyle(Qt::RoundCap);
// 创建一个渐变,将颜色从白色渐变到透明色
QLinearGradient gradient(0, point.y() - 100, 0, point.y() + 100);
gradient.setColorAt(0, QColor(255, 255, 255));
gradient.setColorAt(1, QColor(255, 255, 255, 0));
// 设置画笔的颜色为渐变
pen.setBrush(gradient);
// 将这个画笔设置给 QPainter
painter.setPen(pen);
// 绘制一条直线
painter.drawLine(QPoint(point.x() - 100, point.y()), QPoint(point.x() + 100, point.y()));
painter.restore();
}
// 中间小圆
void MyChart::DrawCircle_bom_small(QPainter& painter, QPoint & center, int radius)
{
//保存绘图对象
painter.save();
//计算圆的位置和大小
int x = center.x() - radius;
int y = center.y() - radius;
int width = 2 * radius;
int height = 2 * radius;
//创建圆形路径
QPainterPath inCircle;
inCircle.moveTo(center);
inCircle.addEllipse(x, y, width, height);
//设置画刷和画笔
painter.setBrush(QColor(0, 73, 107));
painter.setPen(QColor(255, 255, 255));
//绘制圆形
painter.drawPath(inCircle);
//恢复绘图对象
painter.restore();
}
// 渐变发光内圈
void MyChart::DrawCircle_bom_shine(QPainter &painter, QPoint ¢er, int radius)
{
painter.save();
//计算圆的位置和大小
int x = center.x() - radius;
int y = center.y() - radius;
int width = 2 * radius;
int height = 2 * radius;
//创建渐变对象
QRadialGradient radialGradient(center, radius, center);
radialGradient.setColorAt(0.5, QColor(10, 68, 185, 150));
radialGradient.setColorAt(1.0, Qt::transparent);
//设置画刷
painter.setBrush(QBrush(radialGradient));
painter.setPen(Qt::transparent);
//计算圆形路径
QPainterPath path;
path.addEllipse(x, y, width, height);
//绘制圆形渐变
painter.setClipPath(path);
painter.drawEllipse(x, y, width, height);
//恢复绘图对象
painter.restore();
}
// 最外细圆线
void MyChart::DrawCircle_line(QPainter& painter, QPoint ¢er, int radius, int width)
{
painter.save();
QPainterPath outRing;
QPainterPath inRing;
outRing.moveTo(center);
inRing.moveTo(center);
// 计算弧线的起点和终点位置
QPoint startPoint(center.x() + radius * cos(60 * M_PI / 180), center.y() - radius * sin(60 * M_PI / 180));
QPoint endPoint(center.x() + radius * cos(300 * M_PI / 180), center.y() - radius * sin(300 * M_PI / 180));
outRing.arcTo(center.x() - radius, center.y() - radius, 2 * radius, 2 * radius, -30, 240);
inRing.addEllipse(center.x() - radius + width, center.y() - radius + width, 2 * (radius - width), 2 * (radius - width));
outRing.closeSubpath();
painter.setBrush(QColor(5,228,255));
painter.drawPath(outRing.subtracted(inRing));
painter.restore();
}
// 绘制指针
void MyChart::DrawPointer(QPainter& painter, QPoint ¢er, int radius, int degRotate)
{
//组装点的路径图
QPainterPath pointPath;
pointPath.moveTo(0, 0);
pointPath.moveTo(10,0);
pointPath.lineTo(1,-radius);
pointPath.lineTo(-1,-radius);
pointPath.lineTo(-10,0);
pointPath.arcTo(-10,0,20,20,180,180);
QPainterPath inRing;
inRing.addEllipse(-5,-5,10,10);
painter.save();
// 计算绘制相对原点的坐标系
painter.save();
painter.translate(center);
painter.setPen(Qt::transparent);
//计算并选择绘图对象坐标
painter.rotate(degRotate - 120);
painter.setBrush(QColor(0,255,255));
painter.drawPath(pointPath.subtracted(inRing));
painter.restore();
}
// 动态扇形环
void MyChart::DrawCircle_arc(QPainter& painter, QPoint ¢er, int radius, int degRotate)
{
// 计算绘制相对原点的坐标系
painter.save();
// painter移动至center
painter.translate(center);
// 设置扇形渐变色
QRect rect(-radius, -radius, 2 * radius, 2 * radius);
QConicalGradient gradient(0,0,-70);
gradient.setColorAt(0.1, QColor(255, 88, 127, 200)); // 红色
gradient.setColorAt(0.5, QColor(53, 179, 251, 150)); // 蓝色
painter.setBrush(gradient);
// 绘制扇形
painter.drawPie(rect,210*16,-(degRotate)*16);
painter.restore();
}
// 渐变发光外扇形
void MyChart::DrawCircle(QPainter& painter, QPoint ¢er, int radius)
{
//保存绘图对象
painter.save();
// painter移动至center
painter.translate(center);
//计算大小圆路径
QPainterPath outRing;
QPainterPath inRing;
outRing.moveTo(0,0);
inRing.moveTo(0,0);
outRing.arcTo(-radius,-radius, 2*radius,2*radius,-30,240);
inRing.addEllipse(-radius+50,-radius + 50,2*(radius-50),2*(radius-50));
outRing.closeSubpath();
//设置渐变色k
QRadialGradient radialGradient(0,0,radius,0,0);
radialGradient.setColorAt(1,QColor(0,82,199));
radialGradient.setColorAt(0.92,Qt::transparent);
//设置画刷
painter.setBrush(radialGradient);
painter.setPen(Qt::transparent);
//大圆减小圆
painter.drawPath(outRing.subtracted(inRing));
painter.restore();
}
// 刻度数字
void MyChart::DrawDigital(QPainter& painter, QPoint ¢er, int radius)
{
//保存绘图对象
painter.save();
// painter移动至center
painter.translate(center);
//设置画笔,画笔默认NOPEN
painter.setPen(QColor(250,0,150));
QFont font;
font.setFamily("Arial");
font.setPointSize(8);
font.setBold(true);
painter.setFont(font);
for(int i=0;i<13;++i){
QPointF point(0,0);
painter.save();
point.setX(radius*qCos(((210-i*20)*M_PI)/180));
point.setY(radius*qSin(((210-i*20)*M_PI)/180));
painter.translate(point.x(),-point.y());
painter.rotate(-120+i*20);
painter.drawText(-25, 0, 50, 20,Qt::AlignCenter,QString::number(i*20));
painter.restore();
}
//还原画笔
painter.setPen(Qt::NoPen);
painter.restore();
}
// 绘制刻度
void MyChart::DrawSmallScale(QPainter& painter, QPoint ¢er, int radius)
{
//保存绘图对象
painter.save();
// painter移动至center
painter.translate(center);
//组装点的路径图
QPainterPath pointPath_small;
pointPath_small.moveTo(-2,-2);
pointPath_small.lineTo(2,-2);
pointPath_small.lineTo(2,8);
pointPath_small.lineTo(-2,8);
QPainterPath pointPath_big;
pointPath_big.moveTo(-2,-2);
pointPath_big.lineTo(2,-2);
pointPath_big.lineTo(2,20);
pointPath_big.lineTo(-2,20);
painter.setPen(Qt::transparent);
//绘制121个小点
for(int i=0;i<121;i+=2){
QPointF point(0,0);
painter.save();
point.setX(radius*qCos(((210-i*2)*M_PI)/180));
point.setY(radius*qSin(((210-i*2)*M_PI)/180));
painter.translate(point.x(),-point.y());
painter.rotate(-120+i*2);
if(i<80)
{
painter.setBrush(QColor(255,255,255));
}
if(i>=80)
{
painter.setBrush(QColor(235,70,70));
}
if(i%5 == 0)
{
painter.drawPath(pointPath_big);//绘画大刻度
}else
{
painter.drawPath(pointPath_small);//绘画小刻度
}
painter.restore();
}
painter.restore();
}
//绘制码表盘
void MyChart::DrawDb(QPainter& painter, QPoint & center)
{
int radius = qMin(width() / 2, height() / 2);
// 绘制刻度
DrawSmallScale(painter, center, radius - 40);
// 刻度数字
DrawDigital(painter, center, radius - 50);
// 渐变发光外扇形
DrawCircle(painter, center, radius - 20);
// 动态扇形环
DrawCircle_arc(painter, center, radius - 20, m_curValueDashBoard);
// 绘制指针
DrawPointer(painter, center, radius - 30, m_curValueDashBoard);
// 最外细圆线
DrawCircle_line(painter, center, radius - 20, 4);
// 中间大圈
DrawCircle_bom_small(painter, center, radius - 80);
// 渐变发光内圈,-80用于调节渐变圆圈的宽度
DrawCircle_bom_shine(painter, center, radius - 100);
//中间小圆
DrawCircle_bom_small(painter, center, radius - 120);
//绘制实时时速数字
DrawDbNum(painter, center);
// 绘制一条中间略粗两边略尖的渐变发光线条
DrawDbLightLine(painter, center);
}
// 马表盘
void MyChart::DashBoardUpdate(const QPoint & point)
{
m_painter->translate(point);
// 画一个带有渐变色的圆圈
// 镜像渐变
QRadialGradient ra(0, 0, qMin(width(), height() / 2));
// 设置起点颜色
ra.setColorAt(0, Qt::green);
// 终点颜色
ra.setColorAt(1, Qt::cyan);
m_painter->setBrush(ra);
m_painter->setPen(Qt::NoPen);
m_painter->drawEllipse(QPoint(0, 0), qMin(width(), height()) / 2, qMin(width(), height()) / 2);
qreal angle = 270 * 1.0 / (m_maxValueDashBoard - m_minValueDashBoard);
// 135°为刻度的起始点对应的角度
m_painter->rotate(135);
// 绘制刻度线
m_painter->setPen(QPen(Qt::white, 4));
m_painter->setFont(QFont("微软雅黑", 18));
for (int i = m_minValueDashBoard; i <= m_maxValueDashBoard; ++i) {
if (i % 10 == 0) {
if (135 + angle * i < 270) {
m_painter->rotate(180);
m_painter->drawText(-qMin(width(), height()) / 2 + 30, 10, QString::number(i));
m_painter->rotate(-180);
} else {
m_painter->drawText(qMin(width(), height()) / 2 - 20 - 50, 10, QString::number(i));
}
// 第一个参数是起始点的 x 坐标,第二个参数是起始点的 y 坐标,第三个参数是终止点的 x 坐标,第四个参数是终止点的 y 坐标。
m_painter->drawLine(qMin(width(), height()) / 2 - 20, 0, qMin(width(), height()) / 2, 0);
} else {
m_painter->drawLine(qMin(width(), height()) / 2 - 10, 0, qMin(width(), height()) / 2, 0);
}
m_painter->rotate(angle);
}
// 刻度画完后,将painter旋转至水平向下的角度,以便在中心位置绘制文字
// 之前时顺时针旋转的,现在逆时针旋转45°就可以了
m_painter->rotate( - angle - 45);
// 绘制一个圆圈
// `painter.drawEllipse()` 函数的第二个和第三个参数分别表示椭圆的半长轴长度和半短轴长度。
m_painter->drawEllipse(QPoint(0, 0), 100, 100);
// 绘制文字
m_painter->drawText(QRect(-100, -100, 200, 200), Qt::AlignCenter, QString::number(m_curValueDashBoard));
// painter的x轴回到刻度起始位置
m_painter->rotate(135 + m_curValueDashBoard * angle);
// `width()` 和 `height()` 函数返回的分别是 `QWidget` 控件的宽度和高度。
m_painter->drawLine(100, 0, qMin(width(), height()) / 2 - 20 - 50, 0);
// 绘制扇形渐变色
m_painter->rotate(-135 - m_curValueDashBoard * angle);
m_painter->setBrush(QColor(255, 0, 0, 100));
m_painter->setPen(Qt::NoPen);
m_painter->drawPie(QRect(-qMin(width(), height()) / 2 + 20 + 50,
-qMin(width(), height()) / 2 + 20 + 50,
qMin(width(), height()) - 40 - 100,
qMin(width(), height()) - 40 - 100),
(360 - 135) * 16,
-m_curValueDashBoard * angle * 16);
// 释放画刷
m_painter->setBrush(Qt::NoBrush);
}
void MyChart::drawArcAtWidget(QPainter * painter, const QPoint& center, int radius)
{
int startAngle = -30 * 16; // 弧线的起始角度,从 x 轴负方向开始
int endAngle = -90 * 16 + 180 * 16; // 弧线的终止角度,绘制从正上方到正下方的弧线
int startX = center.x();
int startY = painter->device()->height() * 0.1;
int arcHeight = painter->device()->height() * 0.8;
// 调用 drawArc() 函数绘制弧线
painter->drawArc(startX - radius, startY, radius * 2, arcHeight, startAngle, endAngle);
}
void MyChart::paintEvent(QPaintEvent *event)
{
(void)event;
m_painter->begin(this);
// 左侧码盘刷新
DashBoardUpdate(QPoint(qMin(width(), height() / 2), qMin(width(), height() / 2)));
#if 1
// 将坐标系回到原点
m_painter->resetTransform();
// 曲线图刷新
ChartViewUpdate();
// 右侧码盘刷新
#endif
#if 1
// 右侧码盘刷新
// DashBoardUpdate(QPoint(m_axisEndX + qMin(width(), height()) / 2 + 100, qMin(width(), height()) / 2));
QPoint dbrPoint = QPoint(width() - qMin(width(), height()) / 2, qMin(width(), height()) / 2);
DrawDb(*m_painter, dbrPoint);
#endif
m_painter->end();
}
charttest.h
#ifndef CHARTTEST_H
#define CHARTTEST_H
#include <QWidget>
#include <QTimer>
#include <QTime>
#include "mychart.h"
#include <QGroupBox>
QT_BEGIN_NAMESPACE
namespace Ui { class ChartTest; }
QT_END_NAMESPACE
typedef enum {
TRANS_GL_PHY_SEND = 0,
TRANS_TL_PHY_SEND,
TRANS_GL_PHY_RECV,
TRANS_TL_PHY_RECV,
TRANS_GL_MAC_SEND,
TRANS_TL_MAC_SEND,
TRANS_GL_MAC_RECV,
TRANS_TL_MAC_RECV,
TRANS_GL_LC_SEND,
TRANS_TL_LC_SEND,
TRANS_GL_LC_RECV,
TRANS_TL_LC_RECV,
TRANS_MAX,
} GtDataTransType;
class ChartTest : public QWidget
{
Q_OBJECT
public:
ChartTest(QWidget *parent = nullptr);
~ChartTest();
void initState();
typedef struct {
quint32 userId;
quint32 sfr;
quint32 vol;
} RcvVolInfo;
typedef struct {
QVector<quint32> userOn;
QVector<RcvVolInfo> infoG; // 获取g的吞吐量数据
QVector<QVector<RcvVolInfo>> infoT; // 获取多个t的吞吐量数据
} UserInfo;
typedef struct {
quint32 vol;
quint32 sfr;
} GtTransInfoGnode;
typedef struct {
quint32 userId;
double rcvRat;
} UserRcvRateInfo;
typedef struct {
QVector<UserRcvRateInfo> ratlc;
QVector<UserRcvRateInfo> ratmac;
QVector<UserRcvRateInfo> ratphy;
} RcvRateInfo;
typedef struct {
quint32 phyId;
quint32 sfrLc;
quint32 volLc;
quint32 sfrMac;
quint32 volMac;
quint32 sfrPhy;
quint32 volPhy;
} GtTransInfoUser;
typedef struct {
GtTransInfoGnode infoG[TRANS_MAX];
quint32 userNum;
QVector<GtTransInfoUser> infoT;
} GtDataTransAllInfo;
typedef enum {
RCV_TYPE_LC = 0,
RCV_TYPE_MAC,
RCV_TYPE_PHY,
RCV_TYPE_MAX,
} TransRcvType;
// 原始数据刷新
void VolDataUpdate();
// 码速率计算结果数据刷新
void TransRetUpdate();
// 码速率计算结果txt文本显示
void TextViewRefreshTransRate(RcvRateInfo & m_rcvRat);
protected:
void resizeEvent(QResizeEvent *event);
private slots:
void on_splitter_splitterMoved(int pos, int index);
private:
Ui::ChartTest *ui;
int index = 0;
QTimer timer; //定时器
QVector<MyChart *> m_chart;
QVector<QGroupBox *> m_chartView;
quint32 m_userNum = 5;
RcvRateInfo m_rcvRat;
};
#endif // CHARTTEST_H
charttest.cpp
#include "charttest.h"
#include "ui_charttest.h"
#include <QDebug>
ChartTest::ChartTest(QWidget *parent)
: QWidget(parent)
, ui(new Ui::ChartTest)
{
ui->setupUi(this);
initState();
}
ChartTest::~ChartTest()
{
timer.stop();
delete ui;
}
// 页面大小变化触发
void ChartTest::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}
void ChartTest::initState()
{
m_chart.append(ui->chartViewD0);
m_chart.append(ui->chartViewD1);
m_chart.append(ui->chartViewD2);
m_chartView.append(ui->groupBoxD0);
m_chartView.append(ui->groupBoxD1);
m_chartView.append(ui->groupBoxD2);
ui->groupBox_showSet->setMaximumHeight(50);
ui->groupBox_showSet->setMinimumHeight(50);
MyChart::AxisInfo axisInfo;
// 坐标轴参数初始化设置
axisInfo.titleX = "time";
axisInfo.durationX = 100;
axisInfo.titleYl = "码速率(kbs/s)";
axisInfo.durationYl = 10;
axisInfo.titleYr = "码速率(kbs/s)";
axisInfo.dudrationYr = 10;
for (int i = 0; i < m_chart.size(); ++i) {
m_chart[i]->AxisInit(axisInfo);
// 曲线参数初始化设置
m_chart[i]->AxisCurvesAppend("user100", 3, Qt::cyan);
m_chart[i]->AxisCurvesAppend("user101", 3, Qt::green);
m_chart[i]->AxisCurvesAppend("user102", 3, Qt::blue);
m_chart[i]->AxisCurvesAppend("user103", 3, Qt::yellow);
m_chart[i]->AxisCurvesAppend("user104", 3, Qt::magenta);
}
connect(&timer, &QTimer::timeout, [=]()
{
// 码速率计算结果数据刷新
TransRetUpdate();
TextViewRefreshTransRate(m_rcvRat);
// 模拟数据
int y = rand() % 4 + 3;
static int value = 0;
DataNode node = {y, "ABC" + QString::number(value++)};
// 刷新数据
for (int i = 0; i < m_chart.size(); ++i) {
m_chart[i]->updateValue(node);
}
});
timer.start(100);
}
// 码速率计算结果数据刷新
void ChartTest::TransRetUpdate()
{
// lc
m_rcvRat.ratlc.clear();
static double lcRatG = 120.0;
UserRcvRateInfo lcInfoG = {0, lcRatG};
m_rcvRat.ratlc.append(lcInfoG);
for (quint32 i = 0; i < m_userNum; ++i) {
UserRcvRateInfo inf = {100 + i, double(20 + i * 10)};
m_rcvRat.ratlc.append(inf);
}
// mac
m_rcvRat.ratmac.clear();
static double macRatG = 120.0;
UserRcvRateInfo macInfoG = {0, macRatG};
m_rcvRat.ratmac.append(macInfoG);
for (quint32 i = 0; i < m_userNum; ++i) {
UserRcvRateInfo inf = {100 + i, double(20 + i * 10)};
m_rcvRat.ratmac.append(inf);
}
// phy
m_rcvRat.ratphy.clear();
static double phyRatG = 120.0;
UserRcvRateInfo phyInfoG = {0, phyRatG};
m_rcvRat.ratphy.append(phyInfoG);
for (quint32 i = 0; i < m_userNum; ++i) {
UserRcvRateInfo inf = {100 + i, double(20 + i * 10)};
m_rcvRat.ratphy.append(inf);
}
if (lcRatG++ >= 130) {
lcRatG = 120;
}
if (macRatG++ >= 130) {
macRatG = 120;
}
if (phyRatG++ >= 130) {
phyRatG = 120;
}
}
void ChartTest::TextViewRefreshTransRate(RcvRateInfo & info)
{
QDateTime current_time = QDateTime::currentDateTime();
QString time = current_time.toString("yyyy-MM-dd hh:mm:ss");
// QString str = "[" + time + "]\r\n";
QString str = "";
// lc
str += "[" + time + "] " + "lc recv rate : ";
for (int i = 0; i < info.ratlc.size(); ++i) {
str += "user[" + QString::number(info.ratlc[i].userId) + "] : " + QString::number(info.ratlc[i].rcvRat) + ", ";
}
str += "\n";
// mac
str += "[" + time + "] " + "mac recv rate : ";
for (int i = 0; i < info.ratmac.size(); ++i) {
str += "user[" + QString::number(info.ratmac[i].userId) + "] : " + QString::number(info.ratmac[i].rcvRat) + ", ";
}
str += "\n";
// phy
str += "[" + time + "] " + "phy recv rate : ";
for (int i = 0; i < info.ratphy.size(); ++i) {
str += "user[" + QString::number(info.ratphy[i].userId) + "] : " + QString::number(info.ratphy[i].rcvRat) + ", ";
}
str += "\n";
ui->textEdit_transRate->append(str);
ui->textEdit_originData->append(str);
}
void ChartTest::on_splitter_splitterMoved(int pos, int index)
{
int hi = pos;
if (index == 1) {
hi += ui->groupBox_showSet->height() * 1.0 / 2;
} else {
hi -= ui->groupBox_showSet->height() * 1.0 / 2;
}
double hRate1 = (double)(hi * 1.0 / height());
qDebug() << "index " << index << "rate = " << hRate1 << endl;
if (hRate1 < 0.10) {
ui->groupBox_showSet->setVisible(false);
} else if (hRate1 < 0.20) {
ui->groupBox_showSet->setVisible(true);
m_chartView[0]->setVisible(false);
m_chartView[1]->setVisible(false);
m_chartView[2]->setVisible(false);
} else if (hRate1 < 0.3) {
m_chartView[0]->setVisible(false);
m_chartView[1]->setVisible(false);
m_chartView[2]->setVisible(true);
} else if (hRate1 < 0.4) {
m_chartView[0]->setVisible(false);
m_chartView[1]->setVisible(true);
m_chartView[2]->setVisible(true);
} else {
m_chartView[0]->setVisible(true);
m_chartView[1]->setVisible(true);
m_chartView[2]->setVisible(true);
ui->groupBox_textInfo->setVisible(true);
}
}