Search code examples
c++qtqwt

QwtPlotSpectrogram with log scales


I want to put logarithmic a scale next to spectrogram. I want the displayed image to be the same as for the linear data. The code for the version with linear scales looks like this:

#include <QApplication>
#include <QMainWindow>

#include <qwt_plot.h>
#include <qwt_plot_spectrogram.h>
#include <qwt_matrix_raster_data.h>
#include <qwt_color_map.h>
#include <qwt_scale_engine.h>

int main( int argc, char* argv[] ) {
  QApplication app( argc, argv );
  QMainWindow wnd;

  QVector<double> heat_values( 100 * 100 );
  for( int n = 0; n < 100 * 100; ++n ) {
    heat_values[n] = ( n % 100 ) + n / 100;
  };

  QwtPlotSpectrogram heat;
  auto heat_data = std::make_unique<QwtMatrixRasterData>();
  heat_data->setValueMatrix( heat_values, 100 );
  heat_data->setResampleMode(
      QwtMatrixRasterData::ResampleMode::NearestNeighbour );
  heat_data->setInterval( Qt::XAxis, QwtInterval( 0, 100.0 ) );
  heat_data->setInterval( Qt::YAxis, QwtInterval( 0, 100.0 ) );
  heat_data->setInterval( Qt::ZAxis, QwtInterval( 0, 200.0 ) );

  heat.setDisplayMode( QwtPlotSpectrogram::DisplayMode::ImageMode, true );
  heat.setColorMap( new QwtLinearColorMap( Qt::white, Qt::black ) );
  heat.setData( heat_data.release() );

  QwtPlot p;
  p.setAutoDelete( false );
  heat.attach( &p );
  p.repaint();

  wnd.setCentralWidget( &p );
  wnd.resize( 400, 300 );
  wnd.show();

  return QApplication::exec();
}

and produces the expected result.

enter image description here

However, I want the same image but with different scales, for example logarithmic scales from 1 to 101. But after I change the scales like this:

  p.setAxisScaleEngine( QwtPlot::yLeft, new QwtLogScaleEngine() );
  p.setAxisScale( QwtPlot::yLeft, 1.0, 101.0 );
  p.setAxisScaleEngine( QwtPlot::xBottom, new QwtLogScaleEngine() );
  p.setAxisScale( QwtPlot::xBottom, 1.0, 101.0 );

then the spectrogram is all messed up.

enter image description here

Does anyone know how to just change the displayed scale?

enter image description here

msvc 2017, x64, qwt 6.1.4, qt 5.12.2

Edit:

I can get half way there by defining my own RasterData and mapping the coordinates back into bins, but it's still missing the inverse transformation, so the displayed data is a 'log' version of the original.

class RasterData : public QwtRasterData
{
public:
  double value( double const x, double const y ) const override {  
    int const ix = std::min<int>( std::max<int>( 0, x ), m_cols-1 );
    int const iy = std::min<int>( std::max<int>( 0, y ), m_cols-1 );
    return m_values[iy * m_cols + ix];
  }

  void setValueMatrix( QVector<double> const& values, int const cols ) {
    m_values = values;
    m_cols = cols;
  }

private: 
  QVector<double> m_values;
  int m_cols;
};

then result then looks like this:

enter image description here

But essentially I want to avoid all of these tranformations. I want it to just transform the image data passed in via setValueMatrix into an image using the set color map and stretch that image to fit the plot.


Solution

  • The best way I found to make this work is by deriving from QwtPlotSpectrogram and changing the transformation to linear for the call to draw.

    class PlotSpectrogram : public QwtPlotSpectrogram {
    public:
      void draw(
          QPainter* painter,
          QwtScaleMap const& xMap,
          QwtScaleMap const & yMap,
          QRectF const& canvasRect ) const override {
    
        QwtScaleMap xMapLin( xMap );
        QwtScaleMap yMapLin( yMap );
    
        auto const xi = data()->interval( Qt::XAxis );
        auto const yi = data()->interval( Qt::YAxis );
    
        auto const dx = xMapLin.transform( xMap.s1() );
        xMapLin.setScaleInterval( xi.minValue(), xi.maxValue() );
        auto const dy = yMapLin.transform( yMap.s2() );
        yMapLin.setScaleInterval( yi.minValue(), yi.maxValue() );
    
        xMapLin.setTransformation( new QwtNullTransform() );
        yMapLin.setTransformation( new QwtNullTransform() );
    
        QwtPlotSpectrogram::draw(
            painter, xMapLin, yMapLin, canvasRect.translated( dx, -dy ) );
      }
    };
    

    With main altered for a scale log scale from 20..50 and using PlotSpectrogram

      PlotSpectrogram heat;
      auto heat_data = std::make_unique<QwtMatrixRasterData>();
      heat_data->setValueMatrix( heat_values, 100 );
      heat_data->setInterval( Qt::XAxis, QwtInterval( 0, 100.0 ) );
      heat_data->setInterval( Qt::YAxis, QwtInterval( 0, 100.0 ) );
      heat_data->setInterval( Qt::ZAxis, QwtInterval( 0, 200.0 ) );
    
      heat.setDisplayMode( QwtPlotSpectrogram::DisplayMode::ImageMode, true );
      heat.setColorMap( new QwtLinearColorMap( Qt::white, Qt::black ) );
      heat.setData( heat_data.release() );
    
      QwtPlot p;
    
      p.setAxisScaleEngine( QwtPlot::yLeft, new QwtLogScaleEngine() );
      p.setAxisScale( QwtPlot::yLeft, 20.0, 50.0 );
      p.setAxisScaleEngine( QwtPlot::xBottom, new QwtLogScaleEngine() );
      p.setAxisScale( QwtPlot::xBottom, 20.0, 50.0 );
    
      p.setAutoDelete( false );
      heat.attach( &p );
    

    I then get the desired output

    enter image description here