自定义的Qt直方图控件

就是显示1维数据的控件,输入的是大于零的整形数组,对应着y轴的值,不需要x轴数据,默认是1,2,3,4……它是只读控件。目前做的不够智能,不过没关系,对显示简单的统计数据还是可用的。比如:显示1张灰度图的灰度值像素的统计直方图。该控件在VS2015和Qt5.9上测试通过。显示效果如下:

下面上代码,头文件:

class MHistogram : public QWidget
{
    Q_OBJECT

public:
    MHistogram(QWidget *parernt = nullptr);
    void setValue(const QVector<int>& ibins);

private:
    void paintEvent(QPaintEvent *event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void enterEvent(QEvent *event) override;
    void leaveEvent(QEvent *event) override;
    int calcNearInteger(int value, bool upper0rLower);

private:
    QVector<int> bins;
    QVector<QRectF> rects;
    int hitBin;
};

CPP文件:

MHistogram::MHistogram(QWidget* parent) :
    QWidget(parent), hitBin(-1)
{
    setMouseTracking(true);
}

void MHistogram::setValue(const QVector<int>& ibins)
{
    bins = ibins;
    hitBin = -1;
    update();
}

//---------------------------------------------------------------------------------------
// 计算数值value附近最近的可整除10或100或1000…的数值
// value:输入数值
// upperorLower:指示向上取值还是向下取值
// 返回值:返回附近可整除10或100或1000…的数值
//---------------------------------------------------------------------------------------
int MHistogram::calcNearInteger(int value, bool upperOrLower)
{
    int i = 10;
    while (value / i != 0)
    {
        i *= 10;
    }
    i = qMax(i / 10, 10);
    int x = value / i;
    if (upperOrLower)
    {
        return i * (x + 1);
    }
    return i * x;
}

void MHistogram::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    QFontMetrics fm = painter.fontMetrics();
    painter.setBrush(Qt::NoBrush);
    painter.setPen(Qt::black);

    const QMargins margin(40, 20, 20, 20);
    /* 绘制原点 */
    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();

    /* 绘制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();

    const qreal upRatio = 0.9;
    const qreal lowRatio = 0.1;
    auto maxv = bins.empty() ? 0 : *std::max_element(bins.begin(), bins.end());
    maxv = calcNearInteger(maxv, true);
    auto minv = bins.empty() ? 0 : *std::min_element(bins.begin(), bins.end());
    minv = calcNearInteger(minv, false);
    /* 绘制Y轴上的刻度和对应的数字,只画5个刻度 */
    qreal yMaxLength = qAbs(axisy.y() - axiso.y()) * upRatio;
    qreal yMinLength = qAbs(axisy.y() - axiso.y()) * lowRatio;
    int loopCount = 5;
    if (minv == 0) /* 如数据最小是0,则扩展到原点分为5份 */
    {
        yMinLength = 0;
        loopCount = 4;
    }
    int ystart = axiso.y() - (int)yMaxLength;
    int yend = axiso.y() - (int)yMinLength;
    for (int i = 0; i <= loopCount; i++)
    {
        int y = ystart + (yend - ystart) / 5 * i;
        QPoint keDuY1(axiso.x(), y);
        QPoint keDuY2(axiso.x() + 5, 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);
    }

    rects.clear();
    const qreal xRatio = 0.9;
    /* 绘制每个条形 */
    qreal xLength = (axisx.x() - axiso.x()) * xRatio;
    int count = (int)bins.size();
    qreal binWidth = xLength / count;
    for (int i = 0; i < count; i++)
    {
        if (i == hitBin)
        {
            painter.setBrush(QColor(32, 199, 255));
            painter.setPen(QColor(32, 147, 193));
        }
        else
        {
            painter.setBrush(QColor(32, 173, 243));
            painter.setPen(QColor(32, 127, 173));
        }
        qreal binLen = yMinLength + (yMaxLength - yMinLength) * (bins[i] - minv) / (maxv - minv);
        QRectF binRect;
        binRect.setX(axiso.x() + i * binWidth + 1);
        binRect.setY(axiso.y() - binLen);
        binRect.setWidth(binWidth);
        binRect.setHeight(binLen);
        painter.drawRect(binRect);
        rects.push_back(binRect);
    }
}

void MHistogram::mouseMoveEvent(QMouseEvent *event)
{
    int entered = -1;
    int count = (int)bins.size();
    for (int i = 0; i < count; i++)
    {
        if (rects[i].contains(event->pos()))
        {
            entered = i;
            break;
        }
    }
    if (entered != hitBin)
    {
        setToolTip((entered >= 0) ? QString(u8"值:%1").arg(bins[entered]) : QString());
        hitBin = entered;
        update();
    }
}

void MHistogram::enterEvent(QEvent *)
{
    // do mothing.
}

void MHistogram::leaveEvent(QEvent *)
{
    hitBin = -1;
    update();
}

 

posted @ 2022-11-10 18:44  兜尼完  阅读(240)  评论(0编辑  收藏  举报