自定义的Qt统计柱状图控件
此控件和统计折线图控件相似,代码也大部分相同。参见“自定义的Qt统计折线图控件”,这里不多叙述。效果图如下:
上代码,头文件:
class MBarChart : public QWidget { Q_OBJECT public: MBarChart(QWidget *parent); QRgb append(const QVector<qreal>& idata); void setDataTag(int i, const QString& tagName); private: struct XInfo; struct YInfo; 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, XInfo& xinfo); void huaKeDuY(QPainter* painter, const QFontMetrics& fm, const QPoint& axiso, const QPoint& axisy, YInfo& yInfo); std::pair<qreal, qreal> minMaxValue(const QVector<QVector<qreal>>& datas); struct XInfo { QVector<qreal> starts; qreal binWidth; }; struct YInfo { 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 MBarChart::margin(40, 20, 20, 25); const qreal MBarChart::maxRatio = 0.9; const qreal MBarChart::minRatio = 0.1; MBarChart::MBarChart(QWidget *parent) : QWidget(parent) { } QRgb MBarChart::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 MBarChart::setDataTag(int i, const QString& tagName) { tags[i] = tagName; update(); } QRgb MBarChart::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> MBarChart::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 MBarChart::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 MBarChart::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 MBarChart::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 MBarChart::huaKeDuX(QPainter* painter, const QFontMetrics& fm, const QPoint& axiso, const QPoint& axisx, XInfo& xInfo) { const int binPad = 20; /* 柱状图每组柱之间的间隔 */ int cols = tags.size(); qreal xMinLength = qAbs(axisx.x() - axiso.x()) * minRatio; qreal xMaxLength = qAbs(axisx.x() - axiso.x()) * maxRatio; qreal step = ((xMaxLength - xMinLength) - (cols - 1) * binPad) / cols; int rows = colors.size(); xInfo.binWidth = step / rows; /* 计算X轴柱的坐标 */ for (int i = 0; i < cols; i++) { qreal xv = axiso.x() + xMinLength + (step + binPad) * i; xInfo.starts.push_back(xv); } /* 画X轴信息字符串 */ const int tagExtend = 14; for (int i = 0; i < cols; i++) { QPoint xcenter((int)(xInfo.starts[i] + step / 2), 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 MBarChart::huaKeDuY(QPainter* painter, const QFontMetrics& fm, const QPoint& axiso, const QPoint& axisy, YInfo& yInfo) { auto minmax = minMaxValue(datas); qreal minv = calcNearNumber(minmax.first, false); qreal maxv = calcNearNumber(minmax.second, true); 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 MBarChart::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坐标刻度位置 */ XInfo xInfo; huaKeDuX(&painter, fm, axiso, axisx, xInfo); /* 绘制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坐标刻度位置 */ YInfo 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(); painter.setPen(Qt::NoPen); for (int j = 0; j < pcount; j++) { qreal x = xInfo.starts[j] + xInfo.binWidth * i; qreal y = yInfo.scrStart + (yInfo.scrEnd - yInfo.scrStart) * (pts[j] - yInfo.minv) / (yInfo.maxv - yInfo.minv); QRectF rect; rect.setX(x); rect.setY(y); rect.setWidth(xInfo.binWidth); rect.setHeight(axiso.y() - y); painter.setBrush(QColor(colors[i])); painter.drawRect(rect); } } }