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.
Related
I render a QGraphicsScene with 4 million tiles (QGraphicsPixmapItem) and in the QGraphicsView I can scroll performantly.
On mouseover I draw a rectangle over the tile, but this is shown VERY sluggishly and bounces back and forth. With a few tiles (10000) this behavior is super smooth. What can I improve/do here? Probably QT needs too long to find the corresponding tile at the mouse position? Is it possible to develop a custom functionality here to find the corresponding tile?
Minimal example:
(You need a png-file (32x32) called tile_empty.png in your working-dir )
#include <QApplication>
#include <QFrame>
#include <QGraphicsPixmapItem>
#include <QGraphicsView>
#include <QHBoxLayout>
#include <QOpenGLWidget>
#include <QStyleOption>
class View : public QFrame
{
QGraphicsView * gView{ nullptr };
public:
explicit View( QWidget *parent = nullptr ) : QFrame( parent ),
gView( new QGraphicsView( this ) )
{
gView->setViewport( new QOpenGLWidget( ) );
gView->setViewportUpdateMode( QGraphicsView::FullViewportUpdate );
gView->setRenderHint( QPainter::Antialiasing, false );
gView->setDragMode( QGraphicsView::RubberBandDrag );
gView->setOptimizationFlags( QGraphicsView::DontSavePainterState );
gView->setTransformationAnchor( QGraphicsView::AnchorUnderMouse );
gView->setAlignment( Qt::AlignTop | Qt::AlignLeft );
auto topLayout = new QGridLayout;
topLayout->addWidget( gView, 1, 0 );
setLayout( topLayout );
}
auto getView() const -> QGraphicsView& { return *gView; }
};
class Tile : public QGraphicsPixmapItem
{
public:
explicit Tile( size_t x, size_t y, const QPixmap &graphic )
{
setZValue( 1 );
setAcceptHoverEvents( true );
setPixmap( graphic );
setPos( QPointF( x * 32 , y * 32 ) );
}
protected:
auto paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget ) -> void override
{
QGraphicsPixmapItem::paint( painter, option, widget );
if ( !( option->state & QStyle::State_MouseOver ) )
return;
auto oldBrush = painter->brush();
painter->setBrush( QColor( 128, 128, 128 ) );
painter->drawRect( 0, 0, 32, 32 );
painter->setBrush( oldBrush );
}
};
class MainWindow : public QWidget
{
QGraphicsScene * scene{ nullptr };
public:
explicit MainWindow( QWidget *parent = nullptr ) : QWidget( parent ),
scene( new QGraphicsScene( this ) )
{
auto pixmap = new QPixmap( "tile_empty.png" );
auto view = new View();
auto layout = new QHBoxLayout;
for ( size_t y=0; y < 2000; y++ )
for ( size_t x=0; x < 2000; x++ )
scene->addItem( new Tile( x, y, *pixmap ) );
view->getView().setScene( scene );
layout->addWidget( view );
setLayout( layout );
setWindowTitle( tr( "TileDemoSlow" ) );
}
};
auto main( int argc, char *argv[] ) -> int
{
QApplication app( argc, argv );
app.setAttribute( Qt::AA_DontCreateNativeWidgetSiblings );
MainWindow window;
window.show();
return app.exec();
}
CMake (Windows) :
cmake_minimum_required(VERSION 3.21)
project(TileDemoSlow VERSION 1.0
DESCRIPTION "TileDemoSlow"
LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
add_executable(TileDemoSlow WIN32)
if ( MSVC )
set_target_properties(TileDemoSlow PROPERTIES LINK_FLAGS "/ignore:4099")
target_compile_options(TileDemoSlow PRIVATE "/WX")
endif()
set_target_properties(TileDemoSlow PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
set_target_properties(TileDemoSlow PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
include(CheckIPOSupported)
check_ipo_supported(RESULT result)
if(result)
set_target_properties(TileDemoSlow PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
find_package(Qt6 REQUIRED COMPONENTS Core Gui OpenGL OpenGLWidgets Widgets)
qt_standard_project_setup()
target_link_libraries(TileDemoSlow PRIVATE Qt6::Core)
target_link_libraries(TileDemoSlow PRIVATE Qt6::Gui)
target_link_libraries(TileDemoSlow PRIVATE Qt6::OpenGL)
target_link_libraries(TileDemoSlow PRIVATE Qt6::OpenGLWidgets)
target_link_libraries(TileDemoSlow PRIVATE Qt6::Widgets)
target_include_directories(TileDemoSlow PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_sources(TileDemoSlow PRIVATE
#Souces
"${CMAKE_CURRENT_SOURCE_DIR}/main.cpp"
)
New insights 1:
I intercepted the events of the scene and measured the time and the GraphicsSceneMouseMove events seem to be performant. However, there is a very slow MetaCall in between, which is probably responsible for the jerking of the mouse pointer. Unfortunately I can't see in the debugger what kind of call it is. Does anyone have any ideas? Am I on the wrong track?
template< typename EnumType >
QString ToString( const EnumType& enumValue )
{
const char* enumName = qt_getEnumName( enumValue );
const QMetaObject* metaObject = qt_getEnumMetaObject( enumValue );
if (metaObject)
{
const int enumIndex = metaObject->indexOfEnumerator( enumName );
return QString("%1::%2::%3").arg( metaObject->className(),
enumName,
metaObject->enumerator( enumIndex ).valueToKey( enumValue ) );
}
return QString("%1::%2").arg(enumName).arg(static_cast<int>(enumValue));
}
class Scene : public QGraphicsScene
{
public:
explicit Scene( QWidget *parent = nullptr ) : QGraphicsScene( parent )
{}
bool event( QEvent *event ) override
{
auto start = std::chrono::high_resolution_clock::now();
const bool ret = QGraphicsScene::event( event );
auto stop = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast< std::chrono::microseconds >( stop - start );
std::cout << "EventType: " << ToString( event->type() ).toStdString() << " -> Duration: " << duration.count()
<< " microseconds " << std::endl;
return ret;
}
};
Output:
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?
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.
I'm running the sample code below with Qt5.11.0 on OSX 10.13.6 (also RHEL 7.6, where the problem also occurs, but not as ugly as on OSX). The test program displays a custom model in a QTableView, with indexWidgets set for several of the columns:
#include <QtCore/QDebug>
#include <QtCore/QAbstractItemModel>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QTableView>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QRadioButton>
class AModel : public QAbstractItemModel
{
public:
int rowCount( const QModelIndex& parent = QModelIndex() ) const override {
return 5;
};
int columnCount( const QModelIndex& parent = QModelIndex() ) const override {
return 5;
};
QModelIndex parent( const QModelIndex& index ) const override {
return QModelIndex();
};
QModelIndex index( int row, int column, const QModelIndex& parent = QModelIndex() ) const override {
if( ( ! parent.isValid() ) &&
row >= 0 && row < 5 &&
column >= 0 && column < 5 ) {
return createIndex( row, column );
} else {
return QModelIndex();
}
};
QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const override {
QVariant qval;
if( index.column() >= 1 && index.column() < 4 ) { return QVariant(); }
switch( role ) {
case Qt::DisplayRole:
qval = QString( "%1,%2" ).arg( index.row() ).arg( index.column() );
break;
default:
qval = QVariant();
break;
}
return qval;
};
};
class AWidget : public QWidget
{
public:
AWidget( QWidget* parent ) : QWidget( parent ) {
QHBoxLayout* l = new QHBoxLayout();
this->setLayout( l );
QRadioButton* save = new QRadioButton( "Save" );
QRadioButton* del = new QRadioButton( "Delete" );
l->addWidget( save );
l->addWidget( del );
};
};
int
main( int argc, char *argv[] ) {
QApplication app( argc, argv );
QMainWindow* mw = new QMainWindow();
AModel* model = new AModel();
QTableView* view = new QTableView();
view->setModel( model );
// view->verticalHeader()->setDefaultSectionSize( 15 );
for( int irow = 0; irow < model->rowCount(); irow++ ) {
QPushButton* pb = new QPushButton( "Mogrify", mw );
QRadioButton* rb = new QRadioButton( "Choose", mw );
AWidget* aw = new AWidget( mw );
QObject::connect( pb, &QPushButton::clicked, [irow](){ qDebug() << "Mogrifying " << irow; } );
QObject::connect( rb, &QRadioButton::clicked, [irow](){ qDebug() << "Choosing " << irow; } );
view->setIndexWidget( model->index( irow, 1 ), pb );
view->setIndexWidget( model->index( irow, 2 ), rb );
view->setIndexWidget( model->index( irow, 3 ), aw );
}
view->resizeColumnsToContents();
mw->setCentralWidget( view );
mw->show();
return app.exec();
}
If I just run this as shown above, the result comes out with all the table-embedded widgets having enough space:
If however I uncomment the call to setDefaultSectionSize() in the code above, the table-embedded widgets do not size themselves the way I'd wish. The QPushButton is cut off at the bottom, the QRadioButton is crammed in with little padding, and the custom composite widget doesn't show up at all:
I've tried all manner of QSizeHint experiments, subclassing, and internet searching to get these embedded widgets to size themselves according to the space available in the table cells, so far to no avail. How do I make these embedded indexWidgets paint themselves so they fit into the cell space provided in the QTableView, when I'm telling the QTableView how big its cells should be?
The problem is not in the QTableView but in your custom widget. The custom widget has a layout that must have margins equal to 0.
class AWidget : public QWidget
{
public:
AWidget( QWidget* parent=nullptr) :
QWidget( parent )
{
QHBoxLayout* l = new QHBoxLayout(this);
l->setContentsMargins(0, 0, 0, 0); // <----
QRadioButton* save = new QRadioButton( "Save" );
QRadioButton* del = new QRadioButton( "Delete" );
l->addWidget( save );
l->addWidget( del );
};
};
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.