Results 1 to 2 of 2

Thread: Horizontal waterfall plot

  1. #1
    Join Date
    Nov 2015
    Posts
    3
    Qt products
    Qt5
    Platforms
    MacOS X Unix/X11 Windows

    Default Horizontal waterfall plot

    Hi,

    I'm just getting my feet wet with Qwt and the first thing I've attempted is a waterfall plot that shows frequency bins (from an FFT) on the vertical axis, time horizontally and represents intensity as Color. From reading a few posts here and following the rasterview and cpuplot examples I've got the orientation and scaling of the data within the plot... I'm using QwtMatrixRasterData (like in the rasterview example) with newest records at the end (like a vertical reverse waterfall) but Displaying the newest data on the rightmost vertical.

    That works fine except that I only get as many records as there are frequency bins.... That is it only plots a square matrix, stopping at the number of frequency bins instead of the number of records in the data. I'm not quite sure I understand how (for which coordinates) the value method is called because it seems to only get called for that many records.

    Is it possible to re-orient the data in the plot in this way using QwtMatrixRasterData just re-implementing value(), or will I have to implement my own QwtRasterData? Or can anyone suggest another type of plot item that would work for this?

    Here's my code (so far just a small self-contained example but this will eventually be getting updates around 4 times per sec):

    -----------------------------------------specplot.h --------------------------------------------------
    #ifndef SPECPLOT_H
    #define SPECPLOT_H
    #include <qwt_plot.h>

    class QwtPlotSpectrogram;

    class SpectrumPlot: public QwtPlot
    {
    Q_OBJECT

    public:
    SpectrumPlot( QWidget * = NULL );

    private:
    QwtPlotSpectrogram *d_spectrogram;
    };


    #endif // SPECPLOT_H



    -----------------------------------------specplot.cpp --------------------------------------------------
    #include <QApplication>
    #include <QVBoxLayout>
    #include <QTime>

    #include <qwt_plot_layout.h>
    #include <qwt_scale_draw.h>
    #include <qwt_scale_widget.h>
    #include <qwt_plot_canvas.h>
    #include <qwt_color_map.h>
    #include <qwt_plot_spectrogram.h>
    #include <qwt_matrix_raster_data.h>
    #include <qwt_plot_magnifier.h>
    #include <qwt_plot_panner.h>

    #include <iostream>

    #include "specplot.h"

    #define HISTORY 60 // seconds

    //================================================== ============================
    class RasterData: public QwtMatrixRasterData
    {
    public:
    RasterData() :
    numBins(10),
    numRecs(36)
    {
    const double matrix[] =
    {
    100,100,100,100,100,100,100,100,100,100,
    00, 00, 00, 00, 00, 00, 00, 00, 00, 00,
    63, 69, 98, 79, 75, 75, 73, 71, 69, 70,
    63, 63, 99, 77, 76, 73, 71, 69, 68, 70,
    62, 63, 96, 75, 75, 70, 66, 65, 66, 66,
    60, 60, 95, 75, 76, 72, 67, 65, 66, 66,
    60, 60, 91, 70, 72, 68, 67, 67, 67, 68,
    61, 60, 91, 65, 65, 62, 64, 65, 66, 66,
    64, 63, 91, 63, 62, 63, 65, 66, 66, 68,
    64, 63, 90, 60, 61, 61, 63, 65, 66, 67,
    63, 61, 90, 63, 63, 62, 64, 66, 65, 66,
    60, 60, 90, 60, 61, 61, 63, 65, 65, 66,
    68, 71, 95, 80, 76, 71, 71, 71, 70, 69,
    64, 65, 99, 75, 72, 69, 68, 68, 67, 67,
    60, 60, 90, 65, 66, 63, 64, 65, 66, 66,
    64, 67, 92, 82, 81, 73, 73, 76, 76, 76,
    72, 76, 98, 87, 86, 79, 79, 82, 79, 76,
    70, 74, 96, 83, 82, 78, 79, 80, 78, 77,
    67, 75, 96, 93, 91, 88, 89, 83, 77, 76,
    64, 77, 98, 93, 91, 87, 84, 77, 76, 77,
    70, 74, 92, 88, 86, 83, 80, 74, 72, 75,
    68, 73, 99, 85, 82, 79, 79, 75, 72, 70,
    60, 60, 95, 70, 72, 70, 68, 69, 68, 69,
    60, 60, 90, 63, 64, 65, 65, 65, 66, 66,
    60, 60, 90, 62, 63, 64, 65, 66, 66, 67,
    60, 60, 90, 60, 61, 63, 66, 67, 67, 66,
    60, 60, 90, 65, 64, 62, 64, 66, 66, 66,
    60, 60, 90, 64, 64, 63, 64, 66, 66, 66,
    60, 60, 90, 60, 61, 61, 63, 65, 66, 66,
    60, 60, 90, 60, 61, 61, 63, 65, 65, 65,
    60, 60, 90, 63, 65, 63, 64, 65, 66, 66,
    60, 60, 90, 60, 62, 62, 63, 65, 66, 66,
    60, 60, 90, 60, 60, 61, 63, 64, 65, 66,
    60, 60, 90, 60, 61, 62, 63, 65, 66, 66,
    60, 60, 90, 60, 60, 61, 63, 64, 65, 66,
    100,100,100,100,100,100,100,100,100,100
    };

    QVector<double> values;

    for ( uint i = 0; i < sizeof( matrix ) / sizeof( double ); i++ )
    values += matrix[i];
    std::cout << "Added " << std::to_string(values.size()/numBins) << " recs" << std::endl;

    // fill up the values matrix to match the size of the plot...
    // can I avoid this??
    for ( int i = values.size(); i < (HISTORY*numBins); i++ )
    values += -1;
    std::cout << "Padded to " << std::to_string(values.size()/numBins) << " recs" << std::endl;

    setValueMatrix( values, numBins );

    setInterval( Qt::XAxis,
    QwtInterval( 0, numBins, QwtInterval::ExcludeMaximum ) );
    setInterval( Qt::YAxis,
    QwtInterval( 0, HISTORY, QwtInterval::ExcludeMaximum ) );
    setInterval( Qt::ZAxis, QwtInterval( 1.0, 100.0 ) );


    //setResampleMode(ResampleMode::BilinearInterpolatio n);
    setResampleMode(ResampleMode::NearestNeighbour);
    }

    // x = plot freq bin
    // y = plot time
    virtual double value( double x, double y ) const
    {
    double dataX, dataY;

    // frequency bins are columns in data, rows on plot
    dataX = y;

    // time - newest on the right
    dataY = numRecs - x;

    return QwtMatrixRasterData::value( dataX , dataY);;
    }

    private:
    const int numBins;
    int numRecs;
    };


    //================================================== ============================
    class ColorMap: public QwtLinearColorMap
    {
    public:
    ColorMap():
    QwtLinearColorMap( Qt::black, Qt::darkRed )
    {
    addColorStop( 0.2, Qt::blue );
    addColorStop( 0.4, Qt::cyan );
    addColorStop( 0.5, Qt::green);
    addColorStop( 0.6, Qt::yellow );
    addColorStop( 0.8, Qt::red );
    }
    };

    //================================================== ============================
    class TimeScaleDraw: public QwtScaleDraw
    {
    public:
    TimeScaleDraw( const QTime &base ):
    baseTime( base )
    {
    }
    virtual QwtText label( double v ) const
    {
    QTime upTime = baseTime.addSecs( static_cast<int>( HISTORY - v ) );
    return upTime.toString();
    }
    private:
    QTime baseTime;
    };

    //================================================== ============================
    SpectrumPlot::SpectrumPlot( QWidget *parent ):
    QwtPlot( parent )
    {
    QwtPlotCanvas *canvas = new QwtPlotCanvas();
    canvas->setBorderRadius( 0 );
    setCanvas( canvas );

    d_spectrogram = new QwtPlotSpectrogram();
    d_spectrogram->setRenderThreadCount( 1 ); // 0 = use system specific thread count

    d_spectrogram->setColorMap( new ColorMap() );

    d_spectrogram->setData( new RasterData() );
    d_spectrogram->attach( this );
    d_spectrogram->setXAxis(QwtPlot::xBottom);
    d_spectrogram->setYAxis(QwtPlot::yRight);

    // Color scale map on left
    const QwtInterval zInterval = d_spectrogram->data()->interval( Qt::ZAxis );
    QwtScaleWidget *leftAxis = axisWidget( QwtPlot::yLeft );
    leftAxis->setColorBarEnabled( true );
    leftAxis->setColorBarWidth( 10 );
    leftAxis->setColorMap( zInterval, new ColorMap() );

    setAxisScale( QwtPlot::yLeft, zInterval.minValue(), zInterval.maxValue() );
    enableAxis( QwtPlot::yLeft, true );


    enableAxis( QwtPlot::yRight );

    plotLayout()->setAlignCanvasToScales( true );

    setAxisTitle( QwtPlot::xBottom, "Time [h:m:s]" );
    setAxisScaleDraw( QwtPlot::xBottom,
    new TimeScaleDraw( QTime::currentTime() ) );
    setAxisScale( QwtPlot::xBottom, HISTORY, 0);
    setAxisLabelRotation( QwtPlot::xBottom, -50.0 );
    setAxisLabelAlignment( QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom );

    setAxisScale( QwtPlot::yRight, 0.0, 10.0 );
    setAxisMaxMinor( QwtPlot::yRight, 10 );

    QwtPlotMagnifier *magnifier = new QwtPlotMagnifier( canvas );
    magnifier->setAxisEnabled( QwtPlot::yLeft, false );
    magnifier->setAxisEnabled( QwtPlot::yRight, false );

    QwtPlotPanner *panner = new QwtPlotPanner( canvas );
    panner->setAxisEnabled( QwtPlot::yLeft, false );
    panner->setAxisEnabled( QwtPlot::yRight, false );
    }

    //== MAIN ================================================== ==================

    int main( int argc, char **argv )
    {
    QApplication a( argc, argv );

    QWidget vBox;
    vBox.setWindowTitle( "Horizontal Waterfall" );

    SpectrumPlot *plot = new SpectrumPlot(&vBox);

    QVBoxLayout *layout = new QVBoxLayout(&vBox);
    layout->addWidget(plot);

    vBox.resize( 800, 600 );
    vBox.show();

    return a.exec();
    }

  2. #2
    Join Date
    Nov 2015
    Posts
    3
    Qt products
    Qt5
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Horizontal waterfall plot

    It's been a while but I'm working on this again and I've made some progress but I'm getting some confusing results when I update the data.

    I ended up subclassing QwtRasterData and making a class very similar to QwtMatrixRasterData, but with a method like this:

    Qt Code:
    1. void OslSpectrumRasterData::addSpectrumRecord(const uint8_t * const values)
    2. {
    3. std::copy(values, values + p_data->numBins, std::back_inserter(p_data->values));
    4.  
    5. // extend the plot time interval to include the new data
    6. const QwtInterval timeIval = interval(Qt::XAxis);
    7. QwtInterval newIval(timeIval.minValue(), timeIval.maxValue() + p_data->delta_t);
    8. setInterval(Qt::XAxis, newIval);
    9. }
    To copy to clipboard, switch view to plain text mode 

    It its value() method, the coords are translated to the integer indexes in the raw data:

    Qt Code:
    1. uint32_t row = uint32_t ( (f - fInterval.minValue()) / p_data->delta_f );
    2. uint32_t col = uint32_t ( (t_flip - tInterval.minValue()) / p_data->delta_t );
    To copy to clipboard, switch view to plain text mode 

    Then if there are more data columns than there are pixels I average the surrounding pixels.

    This works fine until I zoom out a little on the time (horizontal) axis. The plot columns don't seem to be all the same width... When I zoom out to the point where a single data column is only a few pixels wide, certain columns in the plot make that data column a pixel wider than the others. If a column is only one pixel wide it is sometimes is rendered as pixel wide, sometimes two depending on where it is on the plot.

    I'm not sure why this is happening... maybe the way I'm checking if there are more data columns than pixels is wrong... When the axis is scaled I do this:

    Qt Code:
    1. connect(magnifier, &TimeScaleZoom::timeScaleChanged,
    2. [this, plotCanvas](double lengthSeconds)
    3. {
    4. double colsInDisplay = lengthSeconds/UPDATE_RATE;
    5. double rpp = (double)colsInDisplay/(double)plotCanvas->contentsRect().width();
    6.  
    7. p_rasterData->setRecsPerPixel(std::ceil(rpp));
    8. });
    To copy to clipboard, switch view to plain text mode 

    And then in the value() method, I do the time average only if recsPerPixel > 1 Is this a valid way to check?

    Or is this simply because of rounding error in the time values requested for each pixel not lining up with my data? Is there some way to get around that?

Similar Threads

  1. Waterfall plot without interpolation
    By titico in forum Qwt
    Replies: 4
    Last Post: 27th November 2013, 18:03
  2. qwt5.2 to qt6.0 : waterfall
    By janK in forum Qwt
    Replies: 1
    Last Post: 12th July 2013, 08:43
  3. Replies: 23
    Last Post: 9th January 2013, 08:27
  4. x-axis for waterfall plot
    By janK in forum Qwt
    Replies: 8
    Last Post: 29th July 2009, 16:00
  5. waterfall display
    By jmsbc in forum Qt Programming
    Replies: 7
    Last Post: 15th November 2008, 09:29

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.