本控件用来显示一组统计数据,数据必须全为正,以折线图形式显示。可以添加折线颜色样式说明(即下图图表上方的甲公司、乙公司和丙公司的标识),参见文章“自定义的Qt给统计图添加颜色样例控件”。本控件在VS2015和Qt5.9上简单测试通过。控件使用方法是:创建类实例,先调用append函数添加数据,然后setDataTag设置添加数据每个数据的说明(即X轴需要绘制的文本)。目前必须先添加完数据才能调用setDataTag函数,否则会崩溃。下面是效果图:
下面上代码。我为了节省时间,有些代码是从我之前的文章中附的代码复制的(~~偷笑~~)。头文件:
class MLineChart : public QWidget { Q_OBJECT public: MLineChart(QWidget* parent = nullptr); QRgb append(const QVector<qreal>& idata); void setDataTag(int i, const QString& tagName); private: struct AxisInfo; void paintEvent(QPaintEvent *event) override; QRgb obtainColor(); qreal calcBaseNumber(qreal v); qreal calcNearNumber(qreal value, bool upperOrLower); void correctApex(qreal& min, qreal& max); void huaKeDuX(QPainter* painter, const QFontMetrics& fm, const QPoint& axiso, const QPoint& axisx, QVector<qreal>& xpoints); void huaKeDuY(QPainter* painter, const QFontMetrics& fm, const QPoint& axiso, const QPoint& axisy, AxisInfo& yInfo); std::pair<qreal, qreal> minMaxValue(const QVector<QVector<qreal>>& datas); struct AxisInfo { qreal minv; qreal maxv; qreal scrStart; /* 对应于minv的屏幕坐标 */ qreal scrEnd; /* 对应于maxv的屏幕坐标 */ }; private: const static QMargins margin; const static qreal maxRatio; const static qreal minRatio; QVector<QVector<qreal>> datas; QVector<QRgb> colors; QStringList tags; };
CPP文件:
const QMargins MLineChart::margin(40, 20, 20, 25); const qreal MLineChart::maxRatio = 0.9; const qreal MLineChart::minRatio = 0.1; MLineChart::MLineChart(QWidget* parent) : QWidget(parent) { } QRgb MLineChart::append(const QVector<qreal>& idata) { if (datas.empty()) { datas.push_back(idata); int count = (int)idata.size(); for (int i = 0; i < count; i++) { tags.append(QString(u8"第%1组").arg(i + 1)); } } else { int dcount = (int)datas.first().size(); int icount = (int)idata.size(); if (dcount > icount) { QVector<qreal> temp = idata; icount = dcount - icount; for (int i = 0; i < icount; i++) { temp.push_back(0); } datas.push_back(qMove(temp)); } else if (dcount < icount) { QVector<qreal> temp = idata; temp.erase(temp.begin() + dcount, temp.end()); datas.push_back(qMove(temp)); } else { datas.push_back(idata); } } QRgb rgb = obtainColor(); colors.append(rgb); update(); return rgb; } void MLineChart::setDataTag(int i, const QString& tagName) { tags[i] = tagName; update(); } QRgb MLineChart::obtainColor() { int r = qrand() % 223 + 32; int b = qrand() % 223 + 32; int g = 255 - qMin(r, b); return qRgb(r, g, b); } std::pair<qreal, qreal> MLineChart::minMaxValue(const QVector<QVector<qreal>>& datas) { std::pair<qreal, qreal> mmValue(0, 0); if (datas.empty()) { return mmValue; } QVector<qreal> minmaxs; for (auto& it : datas) { auto apex = std::minmax_element(it.begin(), it.end()); minmaxs.push_back(*apex.first); minmaxs.push_back(*apex.second); } auto apex = std::minmax_element(minmaxs.begin(), minmaxs.end()); mmValue.first = *apex.first; mmValue.second = *apex.second; return mmValue; } void MLineChart::correctApex(qreal& min, qreal& max) { if (min == 0 && max == 0) { max = 1; } else if (min > 0 && max / min > 10) { min = 0; } else if (max < 0 && min / max > 10) { max = 0; } else if (min * max < 0 && max / min < -10) { min = max / -10; } else if (min * max < 0 && min / max < -10) { max = min / -10; } } qreal MLineChart::calcBaseNumber(qreal v) { qreal i = 1e-34; while (qAbs(v / i) >= 1) { i *= 10; } i /= 10; return i; } //--------------------------------------------------------------------------------------- // 计算数值value附近最近的可整除…0.1或1或10或100或1000…的数值 // value:输入数值 // upperorLower : 指示向上取值还是向下取值 // 返回值:返回附近可整除10或100或1000…的数值 //--------------------------------------------------------------------------------------- qreal MLineChart::calcNearNumber(qreal value, bool upperOrLower) { if (value == 0) { return 0; } qreal i = calcBaseNumber(value); int x = int(value / i); qreal y = i * x; if (upperOrLower) { return (value > 0) ? y + i : y; } else { return (value > 0) ? y : y - i; } } void MLineChart::huaKeDuX(QPainter* painter, const QFontMetrics& fm, const QPoint& axiso, const QPoint& axisx, QVector<qreal>& xpoints) { int count = tags.size(); qreal xMinLength = qAbs(axisx.x() - axiso.x()) * minRatio; qreal xMaxLength = qAbs(axisx.x() - axiso.x()) * maxRatio; qreal step = (xMaxLength - xMinLength) / (count - 1); /* 计算X轴刻度坐标 */ for (int i = 0; i < count; i++) { qreal xv = axiso.x() + xMinLength + step * i; xpoints.push_back(xv); } /* 画X轴信息字符串 */ const int tagExtend = 14; for (int i = 0; i < count; i++) { QPointF x1(xpoints[i], axiso.y() - 5); QPointF x2(xpoints[i], axiso.y()); painter->drawLine(x1, x2); QPoint xcenter((int)xpoints[i], axiso.y() + tagExtend); QSize xsz = fm.size(0, tags[i]); painter->drawText(QRect(QPoint(xcenter.x() - xsz.width() / 2, xcenter.y() - xsz.height() / 2), xsz), tags[i]); } } void MLineChart::huaKeDuY(QPainter* painter, const QFontMetrics& fm, const QPoint& axiso, const QPoint& axisy, AxisInfo& yInfo) { auto minmax = minMaxValue(datas); qreal maxv = calcNearNumber(minmax.first, true); qreal minv = calcNearNumber(minmax.second, false); correctApex(minv, maxv); /* 绘制Y轴上的刻度和对应的数字,只画5个刻度 */ qreal yMaxLength = qAbs(axisy.y() - axiso.y()) * maxRatio; qreal yMinLength = qAbs(axisy.y() - axiso.y()) * minRatio; int loopCount = 5; if (minv == 0) /* 如数据最小是0,则扩展到原点分为5份 */ { yMinLength = 0; loopCount = 4; } qreal ymax = axiso.y() - yMaxLength; qreal ymin = axiso.y() - yMinLength; for (int i = 0; i <= loopCount; i++) { qreal y = ymax + (ymin - ymax) / 5.0 * i; QPointF keDuY1(axiso.x(), y); QPointF keDuY2(axiso.x() + 5.0, y); painter->drawLine(keDuY1, keDuY2); QString zhi = QString::number(maxv + (minv - maxv) / 5 * i); QPoint zcenter(axiso.x() - margin.left() / 2, y); QSize zsz = fm.size(0, zhi); painter->drawText(QRect(QPoint(zcenter.x() - zsz.width() / 2, zcenter.y() - zsz.height() / 2), zsz), zhi); } yInfo.minv = minv; yInfo.maxv = maxv; yInfo.scrStart = ymin; yInfo.scrEnd = ymax; } void MLineChart::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QFontMetrics fm = painter.fontMetrics(); painter.setBrush(Qt::NoBrush); painter.setPen(Qt::black); /* 绘制原点 */ QPoint axiso(margin.left(), height() - margin.bottom()); QPoint ocenter(axiso.x() - 7, axiso.y() + 7); QSize osz = fm.size(0, u8"O"); painter.drawText(QRect(QPoint(ocenter.x() - osz.width() / 2, ocenter.y() - osz.height() / 2), osz), u8"O"); /* 绘制X轴 */ QPoint axisx(width() - margin.right(), height() - margin.bottom()); painter.drawLine(axiso, axisx); QPoint jianTouX[] = /* 向右箭头 */ { { -5, 3 }, { 0, 0 }, { -5, -3 }, }; painter.save(); painter.translate(axisx); painter.drawPolyline(jianTouX, 3); painter.restore(); /* 计算X坐标刻度位置 */ QVector<qreal> xpoints; huaKeDuX(&painter, fm, axiso, axisx, xpoints); /* 绘制Y轴 */ QPoint axisy(margin.left(), margin.top()); painter.drawLine(axiso, axisy); QPoint jianTouY[] = /* 向上箭头 */ { { 3, 5 }, { 0, 0 }, { -3, 5 }, }; painter.save(); painter.translate(axisy);; painter.drawPolyline(jianTouY, 3); painter.restore(); /* 计算Y坐标刻度位置 */ AxisInfo yInfo; huaKeDuY(&painter, fm, axiso, axisy, yInfo); /* 画数据 */ int dcount = datas.size(); for (int i = 0; i < dcount; i++) { const QVector<qreal>& pts = datas[i]; int pcount = pts.size(); QPolygonF poly; for (int j = 0; j < pcount; j++) { qreal y = yInfo.scrStart + (yInfo.scrEnd - yInfo.scrStart) * (pts[j] - yInfo.minv) / (yInfo.maxv - yInfo.minv); poly.append(QPointF(xpoints[j], y)); } painter.setPen(QPen(QColor(colors[i]), 2)); painter.drawPolyline(poly); painter.setPen(QPen(QColor(colors[i]), 5)); painter.drawPoints(poly); } }
标签:控件,const,自定义,int,QPoint,qreal,折线图,axiso,painter From: https://www.cnblogs.com/mengxiangdu/p/16887075.html