Qwt 仅重新绘制特定区域

Posted

技术标签:

【中文标题】Qwt 仅重新绘制特定区域【英文标题】:Qwt replot only a specific area 【发布时间】:2018-02-26 16:33:22 【问题描述】:

我有一个包含许多 QwtPlotCurveQwtPlot 视图,我想突出显示/放大(目前只是尝试更改颜色)离鼠标位置最近的点(因为我将显示一些关于这一点的信息用户按下鼠标按钮时的测量值,我想让他知道当前的“目标”是什么点)。

所以我使用QwtPlotPicker 来获取鼠标位置,然后我设置了一个额外的QwtPlotCurve 曲线,这个单点(“目标”)在其他点之上用不同的颜色绘制。

它可以工作,但我可以完成这项工作的唯一方法是调用QwtPlot::replot(),每次移动鼠标时都要调用它(因为我可能要绘制数千个点)。

我只想重绘之前高亮点所在的区域(以恢复默认显示),然后只重绘新高亮点所在的区域。但是当我这样做时,这个(调用repaint(QRect)而不是replot()),什么也没有发生(没有突出显示点),但是,如果我停用窗口,我看到该点被突出显示,所以看起来repaint确实一些工作,但不足以让最终用户看到它......

请注意,我禁用了 Qwt 后备存储功能。

这是我的 MCVE:

widget.h:

#include <QDialog>

class QLabel;
class QwtPlotCurve;
class QwtPlot;
class Dialog : public QDialog

    Q_OBJECT
public:
    Dialog();

public slots:
    void onHovered( const QPointF& pt );

private:
    std::vector<QwtPlotCurve*> curves;
    QwtPlotCurve* highlight;
    std::tuple<QwtPlotCurve*,int,QRect> highlighted;
    QLabel* closestLabel;
    QwtPlot* plot;
;

widget.cpp:

#include "widget.h"

#include <QVBoxLayout>
#include <QLabel>

#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_picker.h>
#include <qwt_plot_canvas.h>
#include <qwt_picker_machine.h>

#include <sstream>

Dialog::Dialog()

    setLayout( new QVBoxLayout() );

    plot = new QwtPlot(this);

    layout()->addWidget( plot );
    layout()->addWidget( closestLabel = new QLabel( this ) );

    for ( int i = 0; i != 5; ++i )
    
        QwtPlotCurve* curve = new QwtPlotCurve();

        QVector<double> x, y;
        for ( int i = 0; i != 10; ++i )
        
            x.push_back( std::rand() );
            y.push_back( std::rand() );
        

        curve->setSamples( x, y );

        curve->setStyle( QwtPlotCurve::Dots );
        curve->setPen( Qt::black, 5 );
        curve->attach(plot);

        curves.push_back( curve );
    

    highlight = new QwtPlotCurve();
    highlight->setSamples( ,  );
    highlight->setStyle( QwtPlotCurve::Dots );
    highlight->setPen( Qt::red, 5 );
    highlight->attach(plot);

    QwtPlotCanvas* canvas = dynamic_cast<QwtPlotCanvas*>( plot->canvas() );
    if ( canvas )
        canvas->setPaintAttribute( QwtPlotCanvas::BackingStore, false );

    plot->replot();

    QwtPlotPicker* picker = new QwtPlotPicker( plot->canvas() );
    picker->setStateMachine(new QwtPickerTrackerMachine());
    connect(picker, SIGNAL(moved(const QPointF&)), this, SLOT(onHovered(const QPointF&)));


// inspired from QwtPlotCurve::closestPoint
int closestPoint( QwtPlotCurve& curve, const QPoint &pos, double *dist )

    const size_t numSamples = curve.dataSize();

    if ( curve.plot() == NULL || numSamples <= 0 )
        return -1;

    const QwtSeriesData<QPointF> *series = curve.data();

    const QwtScaleMap xMap = curve.plot()->canvasMap( curve.xAxis() );
    const QwtScaleMap yMap = curve.plot()->canvasMap( curve.yAxis() );

    const double xPos = xMap.transform( pos.x() );
    const double yPos = yMap.transform( pos.y() );

    int index = -1;
    double dmin = DBL_MAX;

    for ( uint i = 0; i < numSamples; i++ )
    
        const QPointF sample = series->sample( i );

        const double cx = xMap.transform( sample.x() ) - xPos;
        const double cy = yMap.transform( sample.y() ) - yPos;

        const double dist = sqrt( pow(cx,2) + pow(cy,2) );
        if ( dist < dmin )
        
            index = i;
            dmin = dist;
        
    
    if ( dist )
        *dist = dmin;

    return index;


void Dialog::onHovered( const QPointF& pt )

    // mouse moved!

    QwtPlotCurve* closest = NULL;
    int closestIndex = -1;
    double minDist = DBL_MAX;
    for ( auto curve : curves )
    
        double dist;
        int index = closestPoint( *curve, pt.toPoint(), &dist );
        if ( dist < minDist )
        
            minDist = dist;
            closestIndex = index;
            closest = curve;
        
    

    if ( !closest )
        return;

    std::stringstream str;
    QPointF closestPoint = closest->sample(closestIndex);
    str << "Closest point is " << closestPoint.rx() << "," << closestPoint.ry();
    closestLabel->setText( str.str().c_str() );

    if ( std::get<0>( highlighted ) == closest &&
         std::get<1>( highlighted ) == closestIndex )
    
        // highlighted point is unchanged
        return;
    
    else
    
        // highlighted point changed

        const QwtScaleMap xMap = plot->canvasMap( QwtPlot::xBottom );
        const QwtScaleMap yMap = plot->canvasMap( QwtPlot::yLeft );

        const int rectSize = highlight->pen().width() * 2;
        const int x = xMap.transform( closestPoint.rx() );
        const int y = xMap.transform( closestPoint.ry() );
        const QRect cr = plot->canvas()->contentsRect();

        highlight->setSamples(  closestPoint.rx() ,  closestPoint.ry()  );

        QRect smallCR( x - rectSize/2, y - rectSize/2, rectSize, rectSize );

        std::tuple<QwtPlotCurve*,int,QRect> newHighlighted closest, closestIndex, smallCR ;

        QwtPlotCanvas* canvas = dynamic_cast<QwtPlotCanvas*>( plot->canvas() );
        if ( canvas )
        
            if ( std::get<2>( highlighted ) != QRect() )
            
                // repaint previously highlighted area:
                canvas->repaint( std::get<2>( highlighted ) );
            
            // repaint newly highlighted area:
            canvas->repaint( std::get<2>( newHighlighted ) );

            // if you replace lines above by this one, it works!
            //canvas->replot();
        

        highlighted = newHighlighted;
    

main.cpp:

#include <QApplication>
#include "widget.h"
int main( int argc, char* argv[] )

    QApplication app( argc, argv );
    Dialog dlg;

    dlg.show();

    return app.exec();


编辑:

如果我将highlight = new QwtPlotCurve(); 替换为highlight = new MyCurve();MyCurve 定义为:

class MyCurve : public QwtPlotCurve

public:
    void drawSeries( QPainter *painter,
    const QwtScaleMap &xMap, const QwtScaleMap &yMap,
    const QRectF &canvasRect, int from, int to ) const override
    
        static int i = 0;
        if ( dataSize() != 0 )
            std::cout << "PAINTING " << i++ << std::endl;

        QwtPlotCurve::drawSeries( painter, xMap, yMap, canvasRect, from, to );
    
;

然后我看到控制台在调用每个canvas-&gt;repaint 时显示一个新的“PAINTING”,但是红点不可见。现在,如果我将另一个窗口移到我的上方(或按 Alt),则会报告一个新的“PAINTING”,这次最近的点变为红色。正如我所提到的,该方法看起来不错,但不足以让视图按预期重新绘制......

【问题讨论】:

【参考方案1】:

您应该使用QwtPlotDirectPainter,它旨在完全按照您的意愿行事:

QwtPlotDirectPainter 提供了一个 API 来绘制子集(f.e all 添加点)而不擦除/重新绘制绘图画布。

您可以在 Qwt 的“event_filter”示例中使用它:

// Hightlight the selected point
void CanvasPicker::showCursor( bool showIt )

    if ( !d_selectedCurve )
        return;

    QwtSymbol *symbol = const_cast<QwtSymbol *>( d_selectedCurve->symbol() );

    const QBrush brush = symbol->brush();
    if ( showIt )
        symbol->setBrush( symbol->brush().color().dark( 180 ) );

    QwtPlotDirectPainter directPainter;
    directPainter.drawSeries( d_selectedCurve, d_selectedPoint, d_selectedPoint );

    if ( showIt )
        symbol->setBrush( brush ); // reset brush

根据showIt 参数,此函数将把点绘制为“选定”或以其原始/未选定样式重新绘制。

你可以在select()函数中看到它是如何使用的:

void CanvasPicker::select( const QPoint &pos )

    [...]

    showCursor( false ); // Mark the previously selected point as deselected
    d_selectedCurve = NULL;
    d_selectedPoint = -1;

    if ( curve && dist < 10 ) // 10 pixels tolerance
    
        d_selectedCurve = curve;
        d_selectedPoint = index;
        showCursor( true ); // Mark the new point as selected.
    

在您的情况下,我相信您可以直接使用 CanvasPicker 类并进行一些微调,例如在 QEvent::MouseMove 上调用 select() 而不是 QEvent::MouseButtonPress

【讨论】:

这看起来很有趣。对于 +50 的声誉,您能否提供一个答案,并更新我的 MCVE 并努力证明这完全有效? 嗨本杰明,我尝试在我的示例中使用QwtPlotDirectPainter。当鼠标靠近它时使用QwtPlotDirectPainter 而不是调用replot,我可以轻松地将点颜色从黑色更改为红色。美好的。但是当鼠标靠近另一个点而不重新绘制整个QwtPlot 或至少整个QwtPlotCurve 时,我看不到如何将点颜色从红色变为黑色。我觉得这意味着我的代码将发生深刻的变化,而它显然几乎可以工作:正如我所说的“如果我停用窗口,我会看到这一点被突出显示......” @jpo38 我在回答中添加了一些关于如何“取消选择”点的详细信息。我不会费心更新你的 MCVE,因为我的答案已经有了它自己的 MCVE(即来自 Qwt 的 event_filter" 示例)。 问题是这只会隐藏/显示给定曲线的给定图。但是,如果绘图区域显示了许多曲线,并且其中一些在显示/隐藏所选项目的“后面”有一些绘图,我怀疑当所选项目将被绘制/未绘制时它们会被重新绘制。这就是为什么我认为正确的方法是要求使给定的 QRect 无效。否则,我需要找到修改后的所有曲线的所有系列,这些曲线需要重新绘制.... 我刚刚测试过,在 event_filter 例子中改变了曲线的位置。我现在有与蓝色曲线重叠的洋红色曲线。我确认如果我选择/取消选择一个洋红色点,蓝色曲线重叠项目不会按预期重新绘制......

以上是关于Qwt 仅重新绘制特定区域的主要内容,如果未能解决你的问题,请参考以下文章

Qwt 图没有重新绘制

qwt 在重新缩放或缩放光标后绘制移动曲线

QWT zoomer plus panner 连续重绘

Qwt 重新缩放轴

Qwt 追加点到绘图

如何在重新绘制之前清除 QwtPlot 曲线?