前段时间有朋友找我做个问卷调查的软件,我说现在很多在线文档都有这功能,为啥还要使用Qt撸一个,他说要申请软著,我说,欧了。
我们先看看WPS在线问卷的统计中,柱状图统计的效果吧
我认为主要有以下几个关键点吧:
- 要做到根据窗口的宽度,大小自适应,避免硬编码
- 显示信息可配置
- 纵轴坐标文本需要计算宽度,过长时以省略号截尾
- 鼠标移动到柱状条上时,能够显示相应的文本,同时,背景高亮
废话不多说,直接上代码了,ui文件就不放了,界面都是通过painter绘制的,没有任何控件。
BarGraphStatistic.h
#ifndef BARGRAPHSTATISTIC_H
#define BARGRAPHSTATISTIC_H
#include <QWidget>
#include <QVector>
#include <QPair>
namespace Ui {
class BarGraphStatistic;
}
class BarGraphStatistic : public QWidget
{
Q_OBJECT
public:
explicit BarGraphStatistic(const QVector<QPair<QString, int>>& choices, QWidget *parent = nullptr);
~BarGraphStatistic();
protected:
void paintEvent(QPaintEvent* e);
void mouseMoveEvent(QMouseEvent* e);
void resizeEvent(QResizeEvent* e);
private:
void UpdateBarRect();
private:
Ui::BarGraphStatistic *ui;
QVector<QPair<QString, int>> m_choices;
QString m_longestStr{""};
int m_maxCnt{0};
int m_fontSize{12};
int m_padding{20};
int m_itemHeight{45};
int m_maxTxtWidth{200};
int m_txtWidth;
int m_txtHeight;
int m_spaceHorizon{2};
int m_axisXHeight{20};
int m_winHeight;
int m_barStartX;
QVector<QRect> m_barRects;
int m_curBar = -1;
QPoint m_mousePos;
};
#endif // BARGRAPHSTATISTIC_H
BarGraphStatistic.cpp
#include "BarGraphStatistic.h"
#include "ui_BarGraphStatistic.h"
#include <QPainter>
#include <QMouseEvent>
#include <QToolTip>
#include <QTextOption>
BarGraphStatistic::BarGraphStatistic(const QVector<QPair<QString, int>>& choices, QWidget *parent) :
QWidget(parent),
ui(new Ui::BarGraphStatistic),
m_choices(choices)
{
ui->setupUi(this);
setMouseTracking(true);
for (int i = 0; i < m_choices.size(); ++i)
{
if (m_choices[i].first.size() > m_longestStr.size())
m_longestStr = m_choices[i].first;
if (m_choices[i].second > m_maxCnt)
m_maxCnt = m_choices[i].second;
}
QFont f;
f.setPixelSize(m_fontSize);
QFontMetrics fm(f);
auto tr = fm.boundingRect(m_longestStr);
m_txtWidth = qMin(tr.width(), 100);
m_txtHeight = tr.height();
m_winHeight = m_padding * 2 + m_itemHeight * m_choices.size() + m_axisXHeight;
m_barStartX = m_padding + m_txtWidth;
setFixedHeight(m_winHeight);
m_barRects.resize(m_choices.size());
UpdateBarRect();
}
BarGraphStatistic::~BarGraphStatistic()
{
delete ui;
}
void BarGraphStatistic::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
{
// draw background with white color
painter.fillRect(this->rect(), QBrush(Qt::white));
}
{
// draw axis
painter.save();
QPen pen;
pen.setColor(Qt::gray);
painter.setPen(pen);
{
int x0 = m_padding + m_txtWidth + m_spaceHorizon;
int y0 = m_padding;
int x1 = x0;
int y1 = m_winHeight - m_padding - m_axisXHeight;
painter.drawLine(x0, y0, x1, y1);
}
{
int x0 = m_padding + m_txtWidth + m_spaceHorizon;
int y0 = m_winHeight - m_padding - m_axisXHeight;
int x1 = width() - m_padding;
int y1 = y0;
painter.drawLine(x0, y0, x1, y1);
}
{
int x = m_padding + m_txtWidth + m_spaceHorizon;
int y = m_padding + m_itemHeight * m_choices.size();
int w = width() - x - m_padding;
int h = m_axisXHeight;
painter.drawText(x, y, w, h, Qt::AlignLeft, "0");
painter.drawText(x, y, w, h, Qt::AlignRight, QString::number(m_maxCnt + 1));
}
painter.restore();
}
{
painter.save();
QFont f;
f.setPixelSize(m_fontSize);
painter.setFont(f);
for (int i = 0; i < m_choices.size(); ++i)
{
{
int x = m_padding;
int y = m_padding + i * m_itemHeight;
int w = m_txtWidth;
int h = m_itemHeight;
QFontMetrics fm(f);
auto txt = fm.elidedText(m_choices[i].first, Qt::ElideRight, w);
painter.drawText(QRect(x, y, w, h), Qt::AlignVCenter|Qt::AlignRight, txt);
}
{
if (m_curBar == i)
{
painter.fillRect(m_barRects[i], QColor(10, 108, 255, 20));
}
int x = m_padding + m_txtWidth + m_spaceHorizon;
int y = m_padding + i * m_itemHeight + m_itemHeight/4;
int w = (width() - m_padding - m_txtWidth - m_spaceHorizon - m_padding) * m_choices[i].second / (m_maxCnt + 1);
int h = m_itemHeight/2;
painter.fillRect(x, y, w, h, QColor(10, 108, 255));
x = x + w + m_spaceHorizon;
y = m_padding + i * m_itemHeight;
w = (width() - m_padding - m_txtWidth - m_spaceHorizon - m_padding) - w;
h = m_itemHeight;
painter.drawText(x, y, w, h, Qt::AlignVCenter|Qt::AlignLeft, QString::number(m_choices[i].second));
}
}
if (m_curBar >=0 && m_curBar < m_choices.size())
{
painter.save();
auto text = QString("%1: %2").arg(m_choices[m_curBar].first).arg(m_choices[m_curBar].second);
auto font = painter.font();
font.setPixelSize(12);
QFontMetrics fm(font);
auto br = fm.boundingRect(text);
const int maxWidth = 500;
auto w = qMin(maxWidth, br.width()+10);
auto h = br.width() * br.height()/w +20;
QRect rect;
if (m_mousePos.x() + w + 10 > width())
rect = QRect(m_mousePos + QPoint(-10-w, 10), QSize(w, h));
else
rect = QRect(m_mousePos + QPoint(10, 10), QSize(w, h));
painter.setFont(font);
QTextOption tOpt(Qt::AlignTop|Qt::AlignLeft);
tOpt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
painter.drawText(rect, text, tOpt);
painter.restore();
}
painter.restore();
}
}
void BarGraphStatistic::mouseMoveEvent(QMouseEvent *e)
{
int cur = -1;
for (int i = 0; i < m_barRects.size(); ++i)
{
if (m_barRects[i].contains(e->pos()))
{
cur = i;
break;
}
}
// if (cur != m_curBar)
{
m_curBar = cur;
m_mousePos = e->pos();
update();
}
}
void BarGraphStatistic::resizeEvent(QResizeEvent *e)
{
UpdateBarRect();
}
void BarGraphStatistic::UpdateBarRect()
{
for (int i = 0; i < m_choices.size(); ++i)
{
int x = m_padding + m_txtWidth + m_spaceHorizon;
int y = m_padding + i * m_itemHeight;
int w = width() - m_padding - m_txtWidth - m_spaceHorizon - m_padding;
int h = m_itemHeight;
m_barRects[i] = QRect{x, y, w, h};
}
}
下面是我实现的最终效果,是不是还挺像的,如果感觉那里不好,大家就自己去修改源码吧。