准备工作:首先我缺一个绘图的控件,于是在网上找了找,网上给我介绍的有三种。分别是qcustomplot、qwt以及qchart。我第一次使用的是qcustomplot,我发现使用qcustomplot如果不生成静态库再使用的话,运行起来特别特别慢,这就对编程带来了不便。这其实也不能怪人家,它毕竟大小就有1M多,我们一般写代码一个文件才几十K,所以想呀,qcustomplot编译起来肯定很花时间。然后我就使用了QChart类,我听说会玩这个的话,要比另外两种强。qwt我还没碰过。声明一下,我只是谈谈我个人的想法,不喜勿喷!
QChartView是一个窗口,提供显示的地方。而QChart则像是一张图纸,QLineSeries直线或者QSplineSeries平滑线就相当于是画笔了。我使用继承QChartView来做一个方便我对外使用打开通道、关闭通道、添加数据等功能的接口。
class WaveDisplay : public QChartView
使用枚举定义16个通道
//设置通道数
typedef enum
{
Channel1, Channel2, Channel3, Channel4,
Channel5, Channel6, Channel7, Channel8,
Channel9, Channel10, Channel11, Channel12,
Channel13, Channel14, Channel15, Channel16
}Channel;
曲线选择类型,方便后面更改
typedef QLineSeries LineType;//QLineSeries 直线 QSplineSeries 平滑线
整个控件需要用到一些共有变量,每一次打开一个通道的新建一只画笔(QSplineSeries),然后这只画笔还得记录下来,为我后续添加数据使用,我在这里使用的是QHash来保存的,个人感觉这个还是挺好用的,推荐!后面三行用于自动控制刻度范围。
QChart *chart; //图纸
QValueAxis *axisX, *axisY; //坐标轴
QHash seriesHash; //将多条线放在图纸上
QHash minALL; //记录所有曲线的最小值
QHash maxALL; //记录所有曲线的最大值
double Xmax = 0, Ymax = -350;
double Xmin = 0, Ymin = 350;
double Xrang = 3;
初始化工作
WaveDisplay::WaveDisplay(QWidget *parent) : QChartView(parent)
{
mainLayout = new QGridLayout(parent); //居中显示
mainLayout->addWidget(this, 0, 1, 3, 1);
chart = new QChart; //画图纸
chart->legend()->setVisible(false); //曲线文本提示
setChart(chart); //添加图纸
axisX = new QValueAxis; //设置坐标轴
axisY = new QValueAxis;
chart->addAxis(axisX, Qt::AlignBottom); //将坐标轴加到chart上,居下
chart->addAxis(axisY, Qt::AlignLeft); //居左
setRenderHint(QPainter::Antialiasing, true); //反走样
/*特效*/
//setRubberBand(QChartView::RectangleRubberBand); //矩形缩放
//chart->setAnimationOptions(QChart::SeriesAnimations); //曲线动画模式,不能启用这一项或是选择这个选项,这个会导致曲线闪烁
}
打开一个通道
void WaveDisplay::openChannel(Channel channel)//添加一条平滑线并添加到图纸中
{
minALL.insert(channel, 100000);
maxALL.insert(channel, -100000);
if(seriesHash.contains(channel)) //如果重复打开同一个通道就会执行这条语句,主要是防止内存泄漏
{
chart->addSeries(seriesHash[channel]);
chart->setAxisX(axisX, seriesHash[channel]); //将x和y坐标轴与第一条曲线连接
chart->setAxisY(axisY, seriesHash[channel]);
return;
}
LineType *series = new LineType();
series->setPen(Color[channel]);
chart->addSeries(series);
chart->setAxisX(axisX, series); //将x和y坐标轴与第一条曲线连接
chart->setAxisY(axisY, series);
seriesHash.insert(channel, series); //记录到表中
}
颜色如果自己不想配的话可以直接使用我自己配的,配16中人眼看起来完全不同颜色还是挺难受的
namespace ChannelColor{
static QColor Color[16] = {
QColor(165, 42, 42), //Channel1Color
QColor (255, 127, 80), //Channel2Color
QColor (30, 144, 255), //Channel3Color
QColor (218, 165, 32), //Channel4Color
QColor (255, 0, 255), //Channel5Color
QColor (147, 112, 219), //Channel6Color
QColor (0, 255, 255), //Channel7Color
QColor (0, 0, 128), //Channel8Color
QColor (128, 0, 128), //Channel9Color
QColor (46, 139, 87), //Channel10Color
QColor (152, 251, 152), //Channel11Color
QColor (0, 255, 127), //Channel12Color
QColor (0, 0, 255), //Channel13Color
QColor (135, 206, 250), //Channel14Color
QColor (255, 192, 203), //Channel15Color
QColor (127, 255, 0) //Channel16Color
};
}
关闭通道
void WaveDisplay::closeChannel(Channel channel)
{
if(seriesHash.contains(channel)) //如果先前没有打开这通道,程序就不会执行这条复合语句,主要防止用户点错
{
chart->removeSeries(seriesHash[channel]); //先删去图纸上的线
minALL.remove(channel);
maxALL.remove(channel);
}
}
可以在设置数据和追加数据的时候添加判断,这样就可以自动刻度了
void WaveDisplay::setData(Channel channel, const QList& data)
{
if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel]))
{
seriesHash[channel]->clear();
seriesHash[channel]->append(data);
Xmin = data[0].x() < Xmin ? data[0].x() : Xmin;
Xmax = data[data.count() - 1].x() > Xmax ? data[data.count() - 1].x() : Xmax;
for(int i = 0; i < data.count(); i++)
{
maxALL[channel] = data[i].y() > maxALL[channel] ? data[i].y() : maxALL[channel];
minALL[channel] = data[i].y() < minALL[channel] ? data[i].y() : minALL[channel];
}
updateChart();
}
}
追加数据
void WaveDisplay::appendData(Channel channel, const QPointF& data)
{
if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel]))
{
seriesHash[channel]->append(data);
if(data.x() > Xrang)
{
Xmax = data.x();
Xmin = Xmax - Xrang;
}
maxALL[channel] = data.y() > maxALL[channel] ? data.y() : maxALL[channel];
minALL[channel] = data.y() < minALL[channel] ? data.y() : minALL[channel];
updateChart();
}
}
void WaveDisplay::appendData(Channel channel, const QList& data)
{
if(seriesHash.contains(channel) && chart->series().contains(seriesHash[channel]))
{
seriesHash[channel]->append(data);
Xmin = data[0].x() < Xmin ? data[0].x() : Xmin;
Xmax = data[data.count() - 1].x() > Xmax ? data[data.count() - 1].x() : Xmax;
for(int i = 0; i < data.count(); i++)
{
maxALL[channel] = data[i].y() > maxALL[channel] ? data[i].y() : maxALL[channel];
minALL[channel] = data[i].y() < minALL[channel] ? data[i].y() : minALL[channel];
}
updateChart();
}
}
按键点击事件:ESC——恢复默认显示;TAB——支持鼠标取点
void WaveDisplay::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
case Qt::Key_Escape:
updateChart();
break;
case Qt::Key_Tab://Tab键被按下
showPoint = true;
break;
default:
break;
}
}
按键释放事件:TAB——不支持鼠标取点
void WaveDisplay::keyReleaseEvent(QKeyEvent *event)
{
if(event->key() == Qt::Key_Tab)
showPoint = false;
}
鼠标点击事件:左键——取点;右键——配合曲线移动
void WaveDisplay::mouseMoveEvent(QMouseEvent *event)//参考:https://blog.csdn.net/qq_31073871/article/details/83019943
{
QPoint temp = event->pos();//获取当前坐标的位置
if(showPoint)
{
QPointF f = chart->mapToValue(temp);
QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')';
QToolTip::showText(event->globalPos(), str);
}else{
QToolTip::hideText();
}
// getPos(event);
if(event->buttons() == Qt::RightButton){ // 这里必须使用buttons(),因为鼠标移动过程中会检测所有按下的键,而这个时候button()是无法检测那个按键被按下,所以必须使用buttons()函数,看清楚,是buttons()不是button()
QPointF f = chart->mapToValue(temp) - chart->mapToValue(lastPos);
axisX->setRange(axisX->min() - f.x(), axisX->max() - f.x());
axisY->setRange(axisY->min() - f.y(), axisY->max() - f.y());
chart->update();
lastPos = temp;
update();
}
}
鼠标点击事件:左键——取点;右键——移动曲线
void WaveDisplay::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::RightButton){ // 如果是鼠标中键按下
QCursor cursor;
cursor.setShape(Qt::ClosedHandCursor);
QApplication::setOverrideCursor(cursor); // 使鼠标指针暂时改变形状
lastPos = event->pos(); // 获取指针位置和窗口位置的差值,这个globalPos()获取的是鼠标在桌面上的位置,也可以使用pos()函数获取指针在窗口的位置。
}
else if(event->button() == Qt::LeftButton){
getPos(event);
}
}
鼠标移动事件:如果TAB按下则显示鼠标位置;右键:移动曲线
void WaveDisplay::mouseMoveEvent(QMouseEvent *event)
{
QPoint temp = event->pos();//获取当前坐标的位置
if(showPoint)
{
QPointF f = chart->mapToValue(temp);
QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')';
QToolTip::showText(event->globalPos(), str);
}else{
QToolTip::hideText();
}
if(event->buttons() == Qt::RightButton){ // 这里必须使用buttons(),因为鼠标移动过程中会检测所有按下的键,而这个时候button()是无法检测那个按键被按下,所以必须使用buttons()函数,看清楚,是buttons()不是button()
QPointF f = chart->mapToValue(temp) - chart->mapToValue(lastPos);
axisX->setRange(axisX->min() - f.x(), axisX->max() - f.x());
axisY->setRange(axisY->min() - f.y(), axisY->max() - f.y());
chart->update();
lastPos = temp;
update();
}
}
鼠标释放事件:配合曲线移动
void WaveDisplay::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event);
QApplication::restoreOverrideCursor(); // 恢复鼠标指针形状
}
滚轮事件:参考http://blog.csdn.net/smarterr/article/details/80781368
void WaveDisplay::wheelEvent(QWheelEvent *event)
{
if(event->delta() > 0){ // 当滚轮远离使用者时
chart->zoomIn(); // 进行放大
Xrang /= 1.5;
}else{ // 当滚轮向使用者方向旋转时
chart->zoomOut(); // 进行缩小
Xrang *= 1.5;
}
}
获取鼠标坐标函数,自己简单写了下思路,欢迎提问
void WaveDisplay::getPos(QMouseEvent *event)
{
QPoint temp = event->pos();//获取当前坐标的位置
if(showPoint)
{
QPointF f = chart->mapToValue(temp);
QString str = '(' + QString::number(f.x()) + ',' + QString::number(f.y()) + ')';
QToolTip::showText(event->globalPos(), str);
QHash::iterator seriesTemp;
for(seriesTemp = seriesHash.begin(); seriesTemp != seriesHash.end(); ++seriesTemp)//遍历每条曲线
{
QList pointfTemp = seriesTemp.value()->points();//线上所有点对应的屏幕坐标
int low = 0;
int high = pointfTemp.count() - 1;
if(pointfTemp[low].x() > f.x() || pointfTemp[high].x() < f.x())
{
posList[seriesTemp.key()].clear();
continue;
}
while(low < high - 1)//从某条线找到这个点的附近
{
int middle = (low + high) / 2;
if(pointfTemp[middle].x() < f.x())
{
low = middle;
}
if(pointfTemp[middle].x() > f.x())
{
high = middle;
}
}
if(low == high)
posList[seriesTemp.key()] = '(' + QString::number(f.x()) + ',' + QString::number(pointfTemp[high].y()) + ')';
else{
posList[seriesTemp.key()] = '(' + QString::number(f.x()) + ',' + QString::number\
(pointfTemp[low].y() + (pointfTemp[high].y() - pointfTemp[low].y()) / (pointfTemp[high].x() - pointfTemp[low].x()) * (f.x() - pointfTemp[low].x()))\
+ ')';
}
}
}else{
QToolTip::hideText();
}
}
恢复坐标轴
void WaveDisplay::updateChart()
{
double num;
QHash::iterator rangTemp;
//找最小值
if(minALL.size())
{
rangTemp = minALL.begin();
num = rangTemp.value();
++rangTemp;
for(; rangTemp != minALL.end(); ++rangTemp)//遍历每条曲线
{
num = rangTemp.value() < num ? rangTemp.value() : num;
}
Ymin = num;
}
//找最大值
if(maxALL.size())
{
rangTemp = maxALL.begin();
num = rangTemp.value();
++rangTemp;
for(; rangTemp != maxALL.end(); ++rangTemp)//遍历每条曲线
{
num = rangTemp.value() > num ? rangTemp.value() : num;
}
Ymax = num;
}
axisX->setRange(Xmin, Xmax);//设置图表坐标轴的范围,可以不设置,自动调节的
double range = Ymax - Ymin;
axisY->setRange(Ymin - range * 0.1, Ymax + range * 0.1);
}