QwtPlotSpectrogram with log scales - c++

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.
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.
Does anyone know how to just change the displayed scale?
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:
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.

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

QwtPlotMatrixRasterData is not working with non linear scales !
When using QwtRasterData instead everything will work out of the box with any type of scales.

Related

C++ header file issue in VScode

I had created an artificial horizon instrument using Qt designer. I converted the .ui file to python with pyuic. The converted python file is dependent on a C++ and .h file. But the header files in the c++ and .h files are not compatible in vscode.
Below is my python code:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_3 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_3.setObjectName("gridLayout_3")
self.widget_2 = QtWidgets.QWidget(self.centralwidget)
self.widget_2.setObjectName("widget_2")
self.gridLayout_2 = QtWidgets.QGridLayout(self.widget_2)
self.gridLayout_2.setObjectName("gridLayout_2")
spacerItem = QtWidgets.QSpacerItem(20, 460, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 3, 0, 1, 1)
self.label = QtWidgets.QLabel(self.widget_2)
self.label.setObjectName("label")
self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
self.doubleSpinBox = QtWidgets.QDoubleSpinBox(self.widget_2)
self.doubleSpinBox.setMinimum(-90.0)
self.doubleSpinBox.setMaximum(90.0)
self.doubleSpinBox.setObjectName("doubleSpinBox")
self.gridLayout_2.addWidget(self.doubleSpinBox, 0, 1, 1, 1)
self.doubleSpinBox_2 = QtWidgets.QDoubleSpinBox(self.widget_2)
self.doubleSpinBox_2.setMinimum(-180.0)
self.doubleSpinBox_2.setMaximum(180.0)
self.doubleSpinBox_2.setObjectName("doubleSpinBox_2")
self.gridLayout_2.addWidget(self.doubleSpinBox_2, 1, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.widget_2)
self.label_2.setObjectName("label_2")
self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1)
self.gridLayout_3.addWidget(self.widget_2, 0, 0, 1, 1)
self.widget = QtWidgets.QWidget(self.centralwidget)
self.widget.setObjectName("widget")
self.gridLayout = QtWidgets.QGridLayout(self.widget)
self.gridLayout.setObjectName("gridLayout")
spacerItem1 = QtWidgets.QSpacerItem(20, 102, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem1, 0, 1, 1, 1)
spacerItem2 = QtWidgets.QSpacerItem(164, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem2, 1, 0, 1, 1)
self.graphicsEADI = qfi_EADI(self.widget)
self.graphicsEADI.setEnabled(False)
self.graphicsEADI.setMinimumSize(QtCore.QSize(300, 300))
self.graphicsEADI.setMaximumSize(QtCore.QSize(300, 300))
self.graphicsEADI.setFrameShape(QtWidgets.QFrame.NoFrame)
self.graphicsEADI.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.graphicsEADI.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.graphicsEADI.setInteractive(False)
self.graphicsEADI.setObjectName("graphicsEADI")
self.gridLayout.addWidget(self.graphicsEADI, 1, 1, 1, 1)
spacerItem3 = QtWidgets.QSpacerItem(163, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem3, 1, 2, 1, 1)
spacerItem4 = QtWidgets.QSpacerItem(20, 101, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem4, 2, 1, 1, 1)
self.gridLayout_3.addWidget(self.widget, 0, 1, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "Pitch"))
self.label_2.setText(_translate("MainWindow", "Roll"))
from qfi.qfi_EADI import qfi_EADI #it needs to import qfi_EADI which is a cpp file
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Below is my qfi_EADI cpp file
#include <qfi/qfi_AI.h>
#ifdef WIN32
# include <float.h>
#endif
#include <cmath> //gives error
////////////////////////////////////////////////////////////////////////////////
qfi_AI::qfi_AI( QWidget *parent ) :
QGraphicsView ( parent ),
_scene ( Q_NULLPTR ),
_itemBack ( Q_NULLPTR ),
_itemFace ( Q_NULLPTR ),
_itemRing ( Q_NULLPTR ),
_itemCase ( Q_NULLPTR ),
_roll ( 0.0 ),
_pitch ( 0.0 ),
_faceDeltaX_new ( 0.0 ),
_faceDeltaX_old ( 0.0 ),
_faceDeltaY_new ( 0.0 ),
_faceDeltaY_old ( 0.0 ),
_scaleX ( 1.0 ),
_scaleY ( 1.0 ),
_originalHeight ( 240 ),
_originalWidth ( 240 ),
_originalPixPerDeg ( 1.7 ),
_originalAdiCtr ( 120.0 , 120.0 ),
_backZ ( -30 ),
_faceZ ( -20 ),
_ringZ ( -10 ),
_caseZ ( 10 )
{
reset();
_scene = new QGraphicsScene( this );
setScene( _scene );
_scene->clear();
init();
}
////////////////////////////////////////////////////////////////////////////////
qfi_AI::~qfi_AI()
{
if ( _scene != Q_NULLPTR )
{
_scene->clear();
delete _scene;
_scene = Q_NULLPTR;
}
reset();
}
////////////////////////////////////////////////////////////////////////////////
void qfi_AI::reinit()
{
if ( _scene )
{
_scene->clear();
init();
}
}
////////////////////////////////////////////////////////////////////////////////
void qfi_AI::redraw()
{
if ( isVisible() )
{
updateView();
_faceDeltaX_old = _faceDeltaX_new;
_faceDeltaY_old = _faceDeltaY_new;
}
}
////////////////////////////////////////////////////////////////////////////////
void qfi_AI::setRoll( double roll )
{
_roll = roll;
if ( _roll < -180.0 ) _roll = -180.0;
if ( _roll > 180.0 ) _roll = 180.0;
}
////////////////////////////////////////////////////////////////////////////////
void qfi_AI::setPitch( double pitch )
{
_pitch = pitch;
if ( _pitch < -25.0 ) _pitch = -25.0;
if ( _pitch > 25.0 ) _pitch = 25.0;
}
////////////////////////////////////////////////////////////////////////////////
void qfi_AI::resizeEvent( QResizeEvent *event )
{
////////////////////////////////////
QGraphicsView::resizeEvent( event );
////////////////////////////////////
reinit();
}
////////////////////////////////////////////////////////////////////////////////
void qfi_AI::init()
{
_scaleX = static_cast< double >( width() ) / static_cast< double >( _originalWidth );
_scaleY = static_cast< double >( height() ) / static_cast< double >( _originalHeight );
reset();
_itemBack = new QGraphicsSvgItem( ":/qfi/images/ai/ai_back.svg" );
_itemBack->setCacheMode( QGraphicsItem::NoCache );
_itemBack->setZValue( _backZ );
_itemBack->setTransform( QTransform::fromScale( _scaleX, _scaleY ), true );
_itemBack->setTransformOriginPoint( _originalAdiCtr );
_scene->addItem( _itemBack );
_itemFace = new QGraphicsSvgItem( ":/qfi/images/ai/ai_face.svg" );
_itemFace->setCacheMode( QGraphicsItem::NoCache );
_itemFace->setZValue( _faceZ );
_itemFace->setTransform( QTransform::fromScale( _scaleX, _scaleY ), true );
_itemFace->setTransformOriginPoint( _originalAdiCtr );
_scene->addItem( _itemFace );
_itemRing = new QGraphicsSvgItem( ":/qfi/images/ai/ai_ring.svg" );
_itemRing->setCacheMode( QGraphicsItem::NoCache );
_itemRing->setZValue( _ringZ );
_itemRing->setTransform( QTransform::fromScale( _scaleX, _scaleY ), true );
_itemRing->setTransformOriginPoint( _originalAdiCtr );
_scene->addItem( _itemRing );
_itemCase = new QGraphicsSvgItem( ":/qfi/images/ai/ai_case.svg" );
_itemCase->setCacheMode( QGraphicsItem::NoCache );
_itemCase->setZValue( _caseZ );
_itemCase->setTransform( QTransform::fromScale( _scaleX, _scaleY ), true );
_scene->addItem( _itemCase );
centerOn( width() / 2.0 , height() / 2.0 );
updateView();
}
////////////////////////////////////////////////////////////////////////////////
void qfi_AI::reset()
{
_itemBack = Q_NULLPTR;
_itemFace = Q_NULLPTR;
_itemRing = Q_NULLPTR;
_itemCase = Q_NULLPTR;
_roll = 0.0;
_pitch = 0.0;
_faceDeltaX_new = 0.0;
_faceDeltaX_old = 0.0;
_faceDeltaY_new = 0.0;
_faceDeltaY_old = 0.0;
}
////////////////////////////////////////////////////////////////////////////////
void qfi_AI::updateView()
{
_scaleX = static_cast< double >( width() ) / static_cast< double >( _originalWidth );
_scaleY = static_cast< double >( height() ) / static_cast< double >( _originalHeight );
_itemBack->setRotation( - _roll );
_itemFace->setRotation( - _roll );
_itemRing->setRotation( - _roll );
double roll_rad = M_PI * _roll / 180.0;
double delta = _originalPixPerDeg * _pitch;
_faceDeltaX_new = _scaleX * delta * sin( roll_rad );
_faceDeltaY_new = _scaleY * delta * cos( roll_rad );
_itemFace->moveBy( _faceDeltaX_new - _faceDeltaX_old, _faceDeltaY_new - _faceDeltaY_old );
_scene->update();
}
And below is my qfi_AI.h file
#ifndef QFI_AI_H
#define QFI_AI_H
////////////////////////////////////////////////////////////////////////////////
#include <QGraphicsView> //gives error
#include <QGraphicsSvgItem> //gives error
////////////////////////////////////////////////////////////////////////////////
/**
* #brief Attitude Indicator widget class.
*/
class qfi_AI : public QGraphicsView
{
Q_OBJECT
public:
/** Constructor. */
explicit qfi_AI( QWidget *parent = Q_NULLPTR );
/** Destructor. */
virtual ~qfi_AI();
/** Reinitiates widget. */
void reinit();
/** Refreshes (redraws) widget. */
void redraw();
/** #param roll angle [deg] */
void setRoll( double roll );
/** #param pitch angle [deg] */
void setPitch( double pitch );
protected:
/** */
void resizeEvent( QResizeEvent *event );
private:
QGraphicsScene *_scene;
QGraphicsSvgItem *_itemBack;
QGraphicsSvgItem *_itemFace;
QGraphicsSvgItem *_itemRing;
QGraphicsSvgItem *_itemCase;
double _roll;
double _pitch;
double _faceDeltaX_new;
double _faceDeltaX_old;
double _faceDeltaY_new;
double _faceDeltaY_old;
double _scaleX;
double _scaleY;
const int _originalHeight;
const int _originalWidth;
const double _originalPixPerDeg;
QPointF _originalAdiCtr;
const int _backZ;
const int _faceZ;
const int _ringZ;
const int _caseZ;
void init();
void reset();
void updateView();
};
////////////////////////////////////////////////////////////////////////////////
#endif // QFI_AI_H
How do I get rid of the header file issue, should I import the source code for each header file separately or is there a shortcut?

Qwt replot only a specific area

I have a QwtPlot view containing many QwtPlotCurve and I want to highlight/mignify (currently simply trying to change the color) of the closest point to the mouse position (because I'll display some info about this point of measurement when user will press the mouse button, and I'd like him to know what point is the current "target").
So I use a QwtPlotPicker to get mouse position and then I setup an extra QwtPlotCurve curve with this single point ("target") to be drawn with a different color on top of the others.
It works, but the only way I could make this work is by calling QwtPlot::replot() which is heavy to be called every time the mouse is being moved (as I may have many thousands of point being plotted).
I'd like to only repaint the area where previously highlighted point was (to restore default display) and then only repaint the area where newly highlighted point is. But when I do, this (call repaint(QRect) rather than replot()), nothing happens (no point is highlighted), however, if I deactivate the window, I see the point gets highlighted, so it looks like repaint does some piece of job but not enough for the end user to see it...
Note that I disabled Qwt backing store features.
Here is my 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();
}
Edit:
If I replace highlight = new QwtPlotCurve(); by highlight = new MyCurve(); with MyCurve defined as:
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 );
}
};
Then I see that the console show a new "PAINTING" when each canvas->repaint are called, howevere the red point does not become visible. Now if I move another window over mine (or press Alt), a new "PAINTING" is reported and this time the closest point becomes red. So as I mentioned, the method looks good but not enough to have the view be repainted as expected...
You should use QwtPlotDirectPainter, it is designed to do exactly what you want:
QwtPlotDirectPainter offers an API to paint subsets ( f.e all
additions points ) without erasing/repainting the plot canvas.
You can se it being used in the "event_filter" example of Qwt:
// 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
}
Depending on the showIt parameter, this function will either draw the point as "selected" or redraw it in its original/unselected style.
You can see how it is used in the select() function:
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.
}
}
In you case, I believe you could directly use the CanvasPicker class and just do some fine tuning like calling select() on QEvent::MouseMove instead of QEvent::MouseButtonPress.

Qwt - Can we plot an Area chart?

Does anyone know if we can plot an area chart using the Qwt library on Qt creator ? If yes, could someone help me with a sample program ?
Based on the screenshots and the listing of examples, I would look at the source for cpuplot.
./qwt-6.1.2/examples>ls
animation curvdemo1 examples.pro radio simpleplot sysinfo
barchart dials friedberg rasterview sinusplot tvplot
bode distrowatch itemeditor realtime spectrogram
controls event_filter legends refreshtest stockchart
cpuplot examples.pri oscilloscope scatterplot stylesheets
./qwt-6.1.2/examples/cpuplot>ls
cpupiemarker.cpp cpuplot.cpp cpuplot.pro cpustat.h
cpupiemarker.h cpuplot.h cpustat.cpp
And
cpuplot.cpp
#include <qapplication.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qpainter.h>
#include <qwt_plot_layout.h>
#include <qwt_plot_curve.h>
#include <qwt_scale_draw.h>
#include <qwt_scale_widget.h>
#include <qwt_legend.h>
#include <qwt_legend_label.h>
#include <qwt_plot_canvas.h>
#include "cpupiemarker.h"
#include "cpuplot.h"
class TimeScaleDraw: public QwtScaleDraw
{
public:
TimeScaleDraw( const QTime &base ):
baseTime( base )
{
}
virtual QwtText label( double v ) const
{
QTime upTime = baseTime.addSecs( static_cast<int>( v ) );
return upTime.toString();
}
private:
QTime baseTime;
};
class Background: public QwtPlotItem
{
public:
Background()
{
setZ( 0.0 );
}
virtual int rtti() const
{
return QwtPlotItem::Rtti_PlotUserItem;
}
virtual void draw( QPainter *painter,
const QwtScaleMap &, const QwtScaleMap &yMap,
const QRectF &canvasRect ) const
{
QColor c( Qt::white );
QRectF r = canvasRect;
for ( int i = 100; i > 0; i -= 10 )
{
r.setBottom( yMap.transform( i - 10 ) );
r.setTop( yMap.transform( i ) );
painter->fillRect( r, c );
c = c.dark( 110 );
}
}
};
class CpuCurve: public QwtPlotCurve
{
public:
CpuCurve( const QString &title ):
QwtPlotCurve( title )
{
setRenderHint( QwtPlotItem::RenderAntialiased );
}
void setColor( const QColor &color )
{
QColor c = color;
c.setAlpha( 150 );
setPen( QPen( Qt::NoPen ) );
setBrush( c );
}
};
CpuPlot::CpuPlot( QWidget *parent ):
QwtPlot( parent ),
dataCount( 0 )
{
setAutoReplot( false );
QwtPlotCanvas *canvas = new QwtPlotCanvas();
canvas->setBorderRadius( 10 );
setCanvas( canvas );
plotLayout()->setAlignCanvasToScales( true );
QwtLegend *legend = new QwtLegend;
legend->setDefaultItemMode( QwtLegendData::Checkable );
insertLegend( legend, QwtPlot::RightLegend );
setAxisTitle( QwtPlot::xBottom, " System Uptime [h:m:s]" );
setAxisScaleDraw( QwtPlot::xBottom,
new TimeScaleDraw( cpuStat.upTime() ) );
setAxisScale( QwtPlot::xBottom, 0, HISTORY );
setAxisLabelRotation( QwtPlot::xBottom, -50.0 );
setAxisLabelAlignment( QwtPlot::xBottom, Qt::AlignLeft | Qt::AlignBottom );
/*
In situations, when there is a label at the most right position of the
scale, additional space is needed to display the overlapping part
of the label would be taken by reducing the width of scale and canvas.
To avoid this "jumping canvas" effect, we add a permanent margin.
We don't need to do the same for the left border, because there
is enough space for the overlapping label below the left scale.
*/
QwtScaleWidget *scaleWidget = axisWidget( QwtPlot::xBottom );
const int fmh = QFontMetrics( scaleWidget->font() ).height();
scaleWidget->setMinBorderDist( 0, fmh / 2 );
setAxisTitle( QwtPlot::yLeft, "Cpu Usage [%]" );
setAxisScale( QwtPlot::yLeft, 0, 100 );
Background *bg = new Background();
bg->attach( this );
CpuPieMarker *pie = new CpuPieMarker();
pie->attach( this );
CpuCurve *curve;
curve = new CpuCurve( "System" );
curve->setColor( Qt::red );
curve->attach( this );
data[System].curve = curve;
curve = new CpuCurve( "User" );
curve->setColor( Qt::blue );
curve->setZ( curve->z() - 1 );
curve->attach( this );
data[User].curve = curve;
curve = new CpuCurve( "Total" );
curve->setColor( Qt::black );
curve->setZ( curve->z() - 2 );
curve->attach( this );
data[Total].curve = curve;
curve = new CpuCurve( "Idle" );
curve->setColor( Qt::darkCyan );
curve->setZ( curve->z() - 3 );
curve->attach( this );
data[Idle].curve = curve;
showCurve( data[System].curve, true );
showCurve( data[User].curve, true );
showCurve( data[Total].curve, false );
showCurve( data[Idle].curve, false );
for ( int i = 0; i < HISTORY; i++ )
timeData[HISTORY - 1 - i] = i;
( void )startTimer( 1000 ); // 1 second
connect( legend, SIGNAL( checked( const QVariant &, bool, int ) ),
SLOT( legendChecked( const QVariant &, bool ) ) );
}
void CpuPlot::timerEvent( QTimerEvent * )
{
for ( int i = dataCount; i > 0; i-- )
{
for ( int c = 0; c < NCpuData; c++ )
{
if ( i < HISTORY )
data[c].data[i] = data[c].data[i-1];
}
}
cpuStat.statistic( data[User].data[0], data[System].data[0] );
data[Total].data[0] = data[User].data[0] + data[System].data[0];
data[Idle].data[0] = 100.0 - data[Total].data[0];
if ( dataCount < HISTORY )
dataCount++;
for ( int j = 0; j < HISTORY; j++ )
timeData[j]++;
setAxisScale( QwtPlot::xBottom,
timeData[HISTORY - 1], timeData[0] );
for ( int c = 0; c < NCpuData; c++ )
{
data[c].curve->setRawSamples(
timeData, data[c].data, dataCount );
}
replot();
}
void CpuPlot::legendChecked( const QVariant &itemInfo, bool on )
{
QwtPlotItem *plotItem = infoToItem( itemInfo );
if ( plotItem )
showCurve( plotItem, on );
}
void CpuPlot::showCurve( QwtPlotItem *item, bool on )
{
item->setVisible( on );
QwtLegend *lgd = qobject_cast<QwtLegend *>( legend() );
QList<QWidget *> legendWidgets =
lgd->legendWidgets( itemToInfo( item ) );
if ( legendWidgets.size() == 1 )
{
QwtLegendLabel *legendLabel =
qobject_cast<QwtLegendLabel *>( legendWidgets[0] );
if ( legendLabel )
legendLabel->setChecked( on );
}
replot();
}
int main( int argc, char **argv )
{
QApplication a( argc, argv );
QWidget vBox;
vBox.setWindowTitle( "Cpu Plot" );
CpuPlot *plot = new CpuPlot( &vBox );
plot->setTitle( "History" );
const int margin = 5;
plot->setContentsMargins( margin, margin, margin, margin );
QString info( "Press the legend to en/disable a curve" );
QLabel *label = new QLabel( info, &vBox );
QVBoxLayout *layout = new QVBoxLayout( &vBox );
layout->addWidget( plot );
layout->addWidget( label );
vBox.resize( 600, 400 );
vBox.show();
return a.exec();
}
Hope that helps.

Zooming in a QGraphicsScene makes my drawings disappear sometimes

I have a QGraphicsScene where I add a QPixmap composed of 4 images and the borders of each image.
I create a new QPixmap with the total size and then use a QPainter to draw each sub-image in the appropiate place in the bigger pixmap. After one sub-image is done, inmediately draw its borders (this may not be optimal but for now I don't mind).
Once the "final" pixmap is finished, I insert directly to the scene with
scene->addPixmap( total )
Here's the code for the pixmap composition:
QPixmap pixFromCube( PanoramicImages* lim ) const
{
const QSize img_size = getImageSize( lim );
const QSize pano_size( img_size.width() * 4, img_size.height() );
QPixmap toret( pano_size );
if( !toret.isNull() ) {
QPainter painter( &toret );
painter.setRenderHint( QPainter::Antialiasing );
int x( 0 );
QPixmap pix = lim->getCamera1Image();
if( !pix.isNull() ) {
painter.drawPixmap( 0, 0, pix.width(), pix.height(), pix );
drawPixBorder( painter, pix.rect() );
}
x += img_size.width();
pix = lim->getCamera2Image();
if( !pix.isNull() ) {
painter.drawPixmap( x, 0, pix.width(), pix.height(), pix );
drawPixBorder( painter, QRectF( x, 0, pix.width(),
pix.height() ) )
;
}
x += img_size.width();
pix = lim->getCamera3Image();
if( !pix.isNull() ) {
painter.drawPixmap( x, 0, pix.width(), pix.height(), pix );
drawPixBorder( painter, QRectF( x, 0, pix.width(),
pix.height() ) )
;
}
x += img_size.width();
pix = lim->getCamera4Image();
if( !pix.isNull() ) {
painter.drawPixmap( x, 0, pix.width(), pix.height(), pix );
drawPixBorder( painter, QRectF( x, 0, pix.width(),
pix.height() ) )
;
}
}
return toret;
}
And
void drawPixBorder( QPainter& painter, const QRectF rect ) const
{
const QBrush oldBrush = painter.brush();
const QPen oldPen = painter.pen();
QColor color( Qt::blue );
if( timer.isActive() ) {
color = Qt::green;
} else {
color = Qt::red;
}
const QBrush brush( color );
QPen pen( brush, 22 );
const QPointF points[ 5 ] = {
rect.topLeft(),
rect.topRight(),
rect.bottomRight(),
rect.bottomLeft(),
rect.topLeft()
};
painter.setBrush( brush );
painter.setPen( pen );
painter.drawPolyline( points, sizeof( points ) / sizeof( points[ 0 ] ) );
painter.setBrush( oldBrush );
painter.setPen( oldPen );
}
Here's the final pixmap when it's loaded for the first time:
And here after a few zoom-outs:
As you can see, at the right some of the borders are missing. When zooming back again to the inital position, the borders are displayed. If I use a smaller width for the lines (say, 5), the borders disappear sooner.
I've been reading other questions here and in the Qt Forums and tried some suggestions like:
pen.setCosmetic( true );
or
painter.setRenderHint( QPainter::NonCosmeticDefaultPen, false);
or:
painter.setRenderHint( QPainter::Antialiasing );
setting the pen width directly to 0
pen.setWidth( 0 )
and combinations.
Neither of them prevented the borders to disappear and using a bigger width just delays the problem.
Is there a way to show always the borders regardless of the zoom level?
Thanks to #Robert for his help. As he has stated in his answer, the solution was to draw directly in the scene, instead of doing it in the pixmap and then adding it.
For drawing in the scene, I decided to use a QPainterPath:
int x( 0 );
QPainterPath rectPath;
for( unsigned int i( 0 ); i < 4; ++i ) {
rectPath.addRect( QRectF( x, 0, width, height ) );
x += width;
}
QColor color( Qt::blue );
if( timer.isActive() ) {
color = Qt::green;
} else {
color = Qt::red;
}
scene->addPath( rectPath, QPen( color ) );
It is because the painter you are using to create the pixmap does not know anything about the transformations/scale of the graphic scene... A possible solutions would be to draw the rectangles within the scene and not directly to the pixmap.

Overloading of qwt setPen does not work

I wanted to compile the following code:
#include <QApplication>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_grid.h>
#include <qwt_symbol.h>
#include <qwt_legend.h>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QwtPlot plot;
plot.setTitle( "Plot Demo" );
plot.setCanvasBackground( Qt::white );
plot.setAxisScale( QwtPlot::yLeft, 0.0, 10.0);
plot.insertLegend( new QwtLegend() );
QwtPlotGrid *grid = new QwtPlotGrid();
grid->attach( &plot );
QwtPlotCurve *curve = new QwtPlotCurve();
curve->setTitle( "Pixel Count" );
curve->setPen( Qt::blue, 4 ),
curve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
QwtSymbol *symbol = new QwtSymbol( QwtSymbol::Ellipse,
QBrush( Qt::yellow ), QPen( Qt::red, 2 ), QSize( 8, 8 ) );
curve->setSymbol( symbol );
QPolygonF points;
points << QPointF( 0.0, 4.4 ) << QPointF( 1.0, 3.0 )
<< QPointF( 2.0, 4.5 ) << QPointF( 3.0, 6.8 )
<< QPointF( 4.0, 7.9 ) << QPointF( 5.0, 7.1 );
curve->setSamples( points );
curve->attach( &plot );
plot.resize( 600, 400 );
plot.show();
return a.exec();
}
But I get the error "No matching function to call for QwtPlotCurve::setPen(...). Candidate is "QwtPlotCurve::setPen(const QPen&)". But according to the documentation (http://qwt.sourceforge.net/class_qwt_plot_curve.html#ae00bd073a2bcf7c3c810d70af1f86750) this function should be overloaded, on the one hand as setPen(const QPen&), but on the other hand as setPen(const QColor & color, qreal width = 0.0, Qt::PenStyle style Qt::SolidLine), but my QT compiler does not recognize this second overloading variable. Why? I already entered the necessary variables into the *.pro-file.
Edit: I could solve this problem by creating a QPen and added the necessary features manually, but this should only be a temporary fix...