QCustomPlot库画实时曲线自适应优化
为什么要优化
用Qt编写画图曲线时,一般有多种方式,这里主要介绍的是第三方库QCustomPlot来画2D实时曲线,而用这个库画2D实时曲线时,我们一般会用xAxis->rescale()或yAxis->rescale()或者customplot->rescaleAxes()这三个函数来让坐标轴自适应曲线的上下限值,但是有两个问题:
1、特殊直线会和坐标轴重合,被坐标轴覆盖。比如,存在两条直线y=0和y=10。
2、靠近坐标轴边缘位置的坐标点会被覆盖。 比如,存在两条直线y=0和y=10,(0,0)和(0,10)都在坐标轴上,不方便观察。
看起来感觉曲线就没留余地。
两条直线只有一条能看到,y=0的曲线被x轴覆盖了,y=10这条线太靠顶边了。
第一种改法(该方法不好)
直接在修改X轴Y轴的范围:
QCPRange rr= ui->ref_graphics->yAxis->range();
double gap = rr.size();
rr.lower = rr.lower - gap * 0.05;
rr.upper = rr.upper + gap * 0.05;
ui->ref_graphics->yAxis->setRange(rr);
rr = ui->ref_graphics->xAxis->range();
gap = rr.size();
rr.lower = rr.lower - gap * 0.05;
rr.upper = rr.upper + gap * 0.05;
ui->ref_graphics->xAxis->setRange(rr);
ui->ref_graphics->replot();
曲线在变化时,看起来没问题,但是如果只有一条直线时,Y轴的上下限会一直放大。
第二种方案,直接改QCustomPlot.cpp文件代码,完美解决。
找到这个函数:void QCPAxis::rescale(bool onlyVisiblePlottables)
它这个函数的逻辑是:找出窗口里面所有曲线值的上限和下限,然后大范围覆盖小范围,小范围扩展大范围。最后检查一下这个范围是不是大于0或者异常数据,处理这些异常情况后,把坐标轴的上下限修改一下。
这函数有两个情况没考虑:
1、就用曲线的极限值来当做坐标轴显示的极限值,就出现我们上面的情况,太极限了,一般肯定要留个一定的上下限空间比较合理。
2、当是一个直线时,即上下限范围=0,这个情况它认为是异常,直接用默认值处理。
那我们要改就要从这两方面去考虑。直接上代码:
void QCPAxis::rescale(bool onlyVisiblePlottables)
{
QCPRange newRange;
bool haveRange = false;
double maxRangeSize = 0, maxRangeSizeTmp = 0;
foreach (QCPAbstractPlottable *plottable, plottables())
{
if (!plottable->realVisibility() && onlyVisiblePlottables)
continue;
QCPRange plottableRange;
bool currentFoundRange;
QCP::SignDomain signDomain = QCP::sdBoth;
if (mScaleType == stLogarithmic)
signDomain = (mRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive);
if (plottable->keyAxis() == this)
{
plottableRange = plottable->getKeyRange(currentFoundRange, signDomain);
}
else
{
plottableRange = plottable->getValueRange(currentFoundRange, signDomain);
}
if (plottableRange.size() > 0)
{
maxRangeSizeTmp = plottableRange.size() / 50;
}
else
{
if (plottableRange.size() == 0)
{
if (qFuzzyIsNull(plottableRange.upper))
{
maxRangeSizeTmp = 1;
}
else
{
maxRangeSizeTmp = abs(plottableRange.upper) / 50;
}
}
else
{
maxRangeSizeTmp = 1;
}
}
if (maxRangeSize < maxRangeSizeTmp)
{
maxRangeSize = maxRangeSizeTmp;
}
if (currentFoundRange)
{
if (!haveRange)
{
newRange = plottableRange;
}
else
{
newRange.expand(plottableRange);
}
haveRange = true;
}
}
if (haveRange)
{
if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
{
double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
if (mScaleType == stLinear)
{
//newRange.lower = center - mRange.size() / 2;
//newRange.upper = center + mRange.size() / 2;
newRange.lower = center - maxRangeSize;
newRange.upper = center + maxRangeSize;
}
else // mScaleType == stLogarithmic
{
newRange.lower = center / qSqrt(mRange.upper / mRange.lower);
newRange.upper = center * qSqrt(mRange.upper / mRange.lower);
}
}
else
{
newRange.lower = newRange.lower - newRange.size() / 50;
newRange.upper = newRange.upper + newRange.size() / 50;
}
setRange(newRange);
}
}
当newRange的范围>0时,直接扩大2% ;
当newRange的范围=0时,直线是0时,范围取[-1, 1];直线是一个不为0的值a时,a 扩大2%;
当newRange的范围<0时,也范围取[-1, 1];
这结果同时对x轴和y轴都适用。
结果如下:看着舒服多了。