在文章之前,首先看看这篇文章要实现的效果:
对于某些绘图应用,如波形绘制,矩形框缩放并不能很好的满足波形查看要求,需要支持仅水平或仅垂直缩放,Qwt的原生控件并不支持仅水平或垂直框选缩放,因此,需要对Qwt缩放控件进行改造,以实现图中的效果。
Qwt提供了丰富的图形缩放功能,主要有两个类,分别是QwtMagnifier和QwtPlotZoomer,其中,QwtMagnifier完成鼠标滚轮缩放功能,QwtPlotZoomer完成矩形框选缩放功能。这两个类的相关继承关系如下图所示。
QwtMagnifier类提供了缩放坐标轴使能函数,用于控制仅水平或仅垂直缩放功能。因此,通过鼠标滚轮利用原生的Qwt控件是支持仅水平缩放功能的。
void setAxisEnabled( int axis, bool on );
bool isAxisEnabled( int axis ) const;
但对于框选缩放类QwtPlotZoomer,并没有提供上述函数用于实现文章需要的水平框选缩放功能。因此,作者针对QwtPlotZoomer类进行了改造,以支持仅水平缩放功能。改造如下:
1、拷贝QwtPlotZoomer类
将QwtPlotZoomer类源代码拷贝出来,我们重命名为YPlotZoomer。保留QwtPlotZoomer类的所有内容。
2、改造头文件
在YPlotZoomer头文件中增加以下公共函数。
// 增加坐标轴缩放使能函数
void setAxisEnabled( int axis, bool on );
bool isAxisEnabled( int axis ) const;
// 重写Picker中橡皮筋绘制函数
virtual void drawRubberBand( QPainter *painter ) const;
// 重写Picker中橡皮筋掩码区域函数
virtual QRegion rubberBandMask() const;
// 重写Picker中坐标跟踪标绘函数
virtual QwtText trackerTextF( const QPointF &pos ) const;
// end added
3、改造CPP文件
首先,在YPlotZoomer实现文件中增加坐标轴使能参数。
class YPlotZoomer::PrivateData
{
public:
uint zoomRectIndex;
QStack<QRectF> zoomStack;
int maxStackDepth;
// added by Yu.J 增加坐标轴缩放使能
bool isAxisEnabled[QwtPlot::axisCnt];
// end added
};
然后,修改rescale函数,在rescale函数中增加按坐标轴缩放实现代码。rescale函数是缩放末端实现函数,主要思想为通过判断坐标轴缩放使能,设置相应坐标轴的比例尺。
void AMCPlotZoomer::rescale()
{
QwtPlot *plt = plot();
if ( !plt )
return;
const QRectF &rect = d_data->zoomStack[d_data->zoomRectIndex];
if ( rect != scaleRect() )
{
const bool doReplot = plt->autoReplot();
plt->setAutoReplot( false );
// modify by Yu.J 20200915 提供仅横坐标或仅纵坐标缩放的功能
if ( d_data->isAxisEnabled[QwtPlot::xTop] && d_data->isAxisEnabled[QwtPlot::xBottom] )
{
double x1 = rect.left();
double x2 = rect.right();
if ( !plt->axisScaleDiv( xAxis() ).isIncreasing() )
qSwap( x1, x2 );
plt->setAxisScale( xAxis(), x1, x2 );
}
if ( d_data->isAxisEnabled[QwtPlot::yLeft] && d_data->isAxisEnabled[QwtPlot::yRight] )
{
double y1 = rect.top();
double y2 = rect.bottom();
if ( !plt->axisScaleDiv( yAxis() ).isIncreasing() )
qSwap( y1, y2 );
plt->setAxisScale( yAxis(), y1, y2 );
}
// end modify
plt->setAutoReplot( doReplot );
plt->replot();
}
}
最后,重写橡皮筋相关函数。这里主要是框选的时候绘制的是矩形框,但我们设置了仅水平缩放,因此希望修改为按水平进行框选,并且显示文字要显示我们水平框选的范围。核心思想是通过重写QwtPicker类中的橡皮筋相关函数,修改绘制矩形框,修改显示文字。重写代码如下。
// added by Yu.J 定制RubberBand(橡皮筋)绘制
void YPlotZoomer::drawRubberBand( QPainter *painter ) const
{
if ( !isActive() || rubberBand() == NoRubberBand ||
rubberBandPen().style() == Qt::NoPen )
{
return;
}
const QPolygon pa = selection();
if ( pa.count() < 2 )
return;
QRect rect = QRect( pa.first(), pa.last() ).normalized();
// 横轴缩放,扩展绘制矩形选择框
if ( d_data->isAxisEnabled[QwtPlot::yLeft]==false || d_data->isAxisEnabled[QwtPlot::yRight] == false )
{
rect.setTop(0);
rect.setBottom(plot()->height());
}
else if ( d_data->isAxisEnabled[QwtPlot::xBottom]==false || d_data->isAxisEnabled[QwtPlot::xTop] == false )
{
rect.setLeft(0);
rect.setRight(plot()->width());
}
else
{
;
}
QwtPainter::drawRect( painter, rect );
}
// added by Yu.J 定制RubberBand(橡皮筋)绘制
static inline QRegion qwtMaskRegion( const QRect &r, int penWidth )
{
const int pw = qMax( penWidth, 1 );
const int pw2 = penWidth / 2;
int x1 = r.left() - pw2;
int x2 = r.right() + 1 + pw2 + ( pw % 2 );
int y1 = r.top() - pw2;
int y2 = r.bottom() + 1 + pw2 + ( pw % 2 );
QRegion region;
region += QRect( x1, y1, x2 - x1, pw );
region += QRect( x1, y1, pw, y2 - y1 );
region += QRect( x1, y2 - pw, x2 - x1, pw );
region += QRect( x2 - pw, y1, pw, y2 - y1 );
return region;
}
// added by Yu.J 定制RubberBand(橡皮筋)绘制
QRegion YPlotZoomer::rubberBandMask() const
{
QRegion mask;
if ( !isActive() || rubberBand() == NoRubberBand ||
rubberBandPen().style() == Qt::NoPen )
{
return mask;
}
const QPolygon pa = selection();
if ( pa.count() < 2 )
return mask;
const int pw = rubberBandPen().width();
QRect rect = QRect( pa.first(), pa.last() );
// 横轴缩放,扩展绘制矩形选择框
if ( d_data->isAxisEnabled[QwtPlot::yLeft]==false || d_data->isAxisEnabled[QwtPlot::yRight] == false )
{
rect.setTop(0);
rect.setBottom(plot()->height());
}
else if ( d_data->isAxisEnabled[QwtPlot::xBottom]==false || d_data->isAxisEnabled[QwtPlot::xTop] == false )
{
rect.setLeft(0);
rect.setRight(plot()->width());
}
else
{
;
}
mask = qwtMaskRegion( rect.normalized(), pw );
return mask;
}
// added by Yu.J 定制坐标显示
QwtText YPlotZoomer::trackerTextF( const QPointF &pos ) const
{
const QPolygon pa = selection();
QString s;
if ( pa.count() < 2 || m_bIsEnd )
{
s = "x=" + QString::number(pos.x() , 'f' , 2 ) + ",y=" + QString::number(pos.y(), 'f' , 2 );
}
else
{
QRect rect = QRect( pa.first(), pa.last() ).normalized();