QCustomPlot Discussion and Comments

QML IntegrationReturn to overview

Hi,

How QCustomPlot be used in QML?

Hi,
I was interested, if it is possible to wrap QCustomPlot, so it can be used in QML.
I tried with QQuickPaintedItem, and it seems at least possible. Even interaction like mouse clicks works but dragging has its limitation

Here is some code:
CustomPlotItem.h

#pragma once

#include <QtQuick>
class QCustomPlot;

class CustomPlotItem : public QQuickPaintedItem
{
    Q_OBJECT

public:
    CustomPlotItem( QQuickItem* parent = 0 );
    virtual ~CustomPlotItem();

    void paint( QPainter* painter );

    Q_INVOKABLE void initCustomPlot();  

protected:
    void routeMouseEvents( QMouseEvent* event );

    virtual void mousePressEvent( QMouseEvent* event );
    virtual void mouseReleaseEvent( QMouseEvent* event );
    virtual void mouseMoveEvent( QMouseEvent* event );
    virtual void mouseDoubleClickEvent( QMouseEvent* event );

    void setupQuadraticDemo( QCustomPlot* customPlot ); 

private:
    QCustomPlot*         m_CustomPlot;

private slots:
    void graphClicked( QCPAbstractPlottable* plottable );
    void onCustomReplot();    
    void updateCustomPlotSize();
   
};

CustomPlotItem.cpp

#include "CustomPlotItem.h"
#include "qcustomplot.h"
#include <QDebug>

CustomPlotItem::CustomPlotItem( QQuickItem* parent ) : QQuickPaintedItem( parent )    
    , m_CustomPlot( nullptr )
{
    setFlag( QQuickItem::ItemHasContents, true );
    // setRenderTarget(QQuickPaintedItem::FramebufferObject);
    // setAcceptHoverEvents(true);
    setAcceptedMouseButtons( Qt::AllButtons );

    connect( this, &QQuickPaintedItem::widthChanged, this, &CustomPlotItem::updateCustomPlotSize );
    connect( this, &QQuickPaintedItem::heightChanged, this, &CustomPlotItem::updateCustomPlotSize );
}

CustomPlotItem::~CustomPlotItem()
{
    delete m_CustomPlot;
    m_CustomPlot = nullptr;
}

void CustomPlotItem::initCustomPlot()
{
    m_CustomPlot = new QCustomPlot();

    updateCustomPlotSize();

    setupQuadraticDemo( m_CustomPlot );

    connect( m_CustomPlot, &QCustomPlot::afterReplot, this, &CustomPlotItem::onCustomReplot );

    m_CustomPlot->replot();
}


void CustomPlotItem::paint( QPainter* painter )
{
    if (m_CustomPlot)
    {
        QPixmap    picture( boundingRect().size().toSize() );
        QCPPainter qcpPainter( &picture );

        //m_CustomPlot->replot();
        m_CustomPlot->toPainter( &qcpPainter );

        painter->drawPixmap( QPoint(), picture );
    }
}

void CustomPlotItem::mousePressEvent( QMouseEvent* event )
{
    qDebug() << Q_FUNC_INFO;
    routeMouseEvents( event );
}

void CustomPlotItem::mouseReleaseEvent( QMouseEvent* event )
{
    qDebug() << Q_FUNC_INFO;
    routeMouseEvents( event );
}

void CustomPlotItem::mouseMoveEvent( QMouseEvent* event )
{
    routeMouseEvents( event );
}

void CustomPlotItem::mouseDoubleClickEvent( QMouseEvent* event )
{
    qDebug() << Q_FUNC_INFO;
    routeMouseEvents( event );    
}

void CustomPlotItem::graphClicked( QCPAbstractPlottable* plottable )
{
    qDebug() << Q_FUNC_INFO << QString( "Clicked on graph '%1 " ).arg( plottable->name() );
}

void CustomPlotItem::routeMouseEvents( QMouseEvent* event )
{
    if (m_CustomPlot)
    {
        QMouseEvent* newEvent = new QMouseEvent( event->type(), event->localPos(), event->button(), event->buttons(), event->modifiers() );
        //QCoreApplication::sendEvent( m_CustomPlot, newEvent );
        QCoreApplication::postEvent( m_CustomPlot, newEvent );
    }
}

void CustomPlotItem::updateCustomPlotSize()
{
    if (m_CustomPlot)
    {
        m_CustomPlot->setGeometry( 0, 0, width(), height() );
    }
}

void CustomPlotItem::onCustomReplot()
{
    qDebug() << Q_FUNC_INFO;
    update();
}

void CustomPlotItem::setupQuadraticDemo( QCustomPlot* customPlot )
{
    // make top right axes clones of bottom left axes:
    QCPAxisRect* axisRect = customPlot->axisRect();   

    // generate some data:
    QVector<double> x( 101 ), y( 101 );   // initialize with entries 0..100
    QVector<double> lx( 101 ), ly( 101 ); // initialize with entries 0..100
    for (int i = 0; i < 101; ++i)
    {
        x[i] = i / 50.0 - 1;              // x goes from -1 to 1
        y[i] = x[i] * x[i];               // let's plot a quadratic function

        lx[i] = i / 50.0 - 1;             //
        ly[i] = lx[i];                    // linear
    }
    // create graph and assign data to it:
    customPlot->addGraph();
    customPlot->graph( 0 )->setPen( QPen( Qt::red ) );
    customPlot->graph( 0 )->setSelectedPen( QPen( Qt::blue, 2 ) );
    customPlot->graph( 0 )->setData( x, y );

    customPlot->addGraph();
    customPlot->graph( 1 )->setPen( QPen( Qt::magenta ) );
    customPlot->graph( 1 )->setSelectedPen( QPen( Qt::blue, 2 ) );
    customPlot->graph( 1 )->setData( lx, ly );

    // give the axes some labels:
    customPlot->xAxis->setLabel( "x" );
    customPlot->yAxis->setLabel( "y" );
    // set axes ranges, so we see all data:
    customPlot->xAxis->setRange( -1, 1 );
    customPlot->yAxis->setRange( -1, 1 );

    customPlot ->setInteractions( QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables );
    connect( customPlot, SIGNAL( plottableClick( QCPAbstractPlottable*, QMouseEvent* ) ), this, SLOT( graphClicked( QCPAbstractPlottable* ) ) );
}

main.cpp

#include "CustomPlotItem.h"
#include <QtWidgets/QApplication>
#include <QtQuick>

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

    qmlRegisterType<CustomPlotItem>("CustomPlot", 1, 0, "CustomPlotItem");

    QQuickView view(QUrl("qrc:///qml/main.qml"));
    view.setResizeMode(QQuickView::SizeRootObjectToView);
    view.resize(500, 500);
    view.show();

    return a.exec();
}

main.qml

import QtQuick 2.0
import CustomPlot 1.0

Item {

        CustomPlotItem {

            id: customPlot
            anchors.fill: parent

            Component.onCompleted: initCustomPlot()

        }
}

How can I make that zoom working in android?
Let say if I have two buttons + and -.which functions should i trigger from QML?
Thanks

Hey, thanks for the code!

It works great, until one grabs the plot and drags it around.
I have uploaded a 1Mb animated GIF here that shows what is happening:

http://i.giphy.com/3o85xK9YrecKZjEs7e.gif

The plot alternates between moving too far away from the mouse cursor and then jumping back to it.
So it is flickering between two positions. The correct one and the one too far out.

I think there is a problem in Line 79-87 in CustomPlotItem.cpp

void CustomPlotItem::routeMouseEvents( QMouseEvent* event )
{
    if (m_CustomPlot)
    {
        QMouseEvent* newEvent = new QMouseEvent( event->type(), event->localPos(), event->button(), event->buttons(), event->modifiers() );
        //QCoreApplication::sendEvent( m_CustomPlot, newEvent );
        QCoreApplication::postEvent( m_CustomPlot, newEvent );
    }
}

"sendEvent" tends to move the plot too far with a little flickering and "postEvent" tends to move the plot under the mouse cursor with a lot of flickering.
Uncommenting both methods results in almost correct behaviour with just a little flickering, but that just has to be a coincidence.

Does anyone have an idea how to drag the plot smoothly across the screen?

I have written an additional routeWheelEvents() function which doesn't flicker at all, although it is just a copy of routeMouseEvents()

void CustomPlotItem::routeWheelEvents( QWheelEvent* event )
{
    if (m_CustomPlot)
    {
        QWheelEvent* newEvent = new QWheelEvent( event->pos(), event->delta(), event->buttons(), event->modifiers(), event->orientation() );
        QCoreApplication::postEvent( m_CustomPlot, newEvent );
    }
}

void CustomPlotItem::wheelEvent( QWheelEvent *event )
{
    routeWheelEvents( event );
}

Again, if you have an idea, why the wheelEvent is working and the mouseMoveEvent is not, PLEASE let me know :)

Kind regards,

Hannes

Hi, i use the same code for the base but i use the QSceneGraph instead of Qpaint (need less calculs) see this post http://developer.ubuntu.com/api/qml/sdk-14.10/QtQuick.qtquick-visualcanvas-scenegraph/

The method updatePaintNode replace the method paint.

class QuickPlotSgs:public QQuickItem
{
    Q_OBJECT
public:
    QuickPlotSgs(QQuickItem *parent = NULL);


    Q_INVOKABLE void initCustomPlot();
    Q_INVOKABLE void clearGraph();
    Q_INVOKABLE void printGraph();
protected:
    virtual QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *);

The implementation in .c++

QSGNode *QuickPlotSgs::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>(oldNode);
    if (!node) {
        node = new QSGSimpleTextureNode();
    }
    QQuickWindow tempWindow;

    node->setRect(boundingRect());
    node->setTexture(tempWindow.createTextureFromImage(m_CustomPlot->toPixmap().toImage()));
    return node;
}

It works fine, but now i would like to use the mouse wheel to use the zoom, if someone have a code to share ...

Hi modjo,

I have replaced the paint(QPainter* painter) method with updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) as you have described.
For now, I can't tell if it is faster or not, but nevertheless it is still flickering like crazy while dragging the plot :(

I have posted some mouse-wheel-code above.
Look for the functions routeWheelEvents(QWheelEvent* event) and wheelEvent(QWheelEvent* event) in my post from yesterday.

Ok, for the moment i didn't have solution for your problem. For the wheel mouse i made the same method like you ! I didn't saw that's was i'm looking for !
For information i use the QsceneGraph to use Qcustomplot on a raspberry pi 2 (in qml app), but for the moment it didn't work with this. It's ok with Qpaint but it take some ressource of the pi !

jscurlee's post in this thread might be relevant to the issue at hand:
http://www.qcustomplot.com/index.php/support/forum/746

I'm experiencing the same issue that Hannes has described and shown in the gif. I've tried subclassing QCustomPlot and accepting the press event in mousePressEvent just like described in the other thread but I guess that's a solution for integrating correctly with QGraphicsScene which isn't applicable with QtQuick2 and the scenegraph. Is there any other workaround for this issue?

Thanks for the hint, Manu!
I have also tried to subclass the mouseEvents as described by jscurlee:

class MyQCustomPlot : public QCustomPlot
{
public:
    void mousePressEvent(QMouseEvent *event) {
        QCustomPlot::mousePressEvent(event);
        event->accept();
    }
    void mouseMoveEvent(QMouseEvent *event) {
        QCustomPlot::mouseMoveEvent(event);
        event->accept();
    }
};

But it did nothing.
The thing is, that the mouseMoveEvent() basically works, but someone seems to multiply the x/y values every now and then, which is strange.
Why does it multiply ONLY when the mouse is in motion? And how does the plot know where to snap back to when the mouse is not moving anymore with the button still pressed?! O_o

In the meanwhile I was trying to figure out what gets triggered on QCoreApplication::postEvent( m_CustomPlot, newEvent );, but so far no luck :(

Well, QCoreApplication::postEvent( m_CustomPlot, newEvent ) will result in actually triggering events like mousePressEvent(QMouseEvent *event) on the QCustomPlot object, or, in the case that you've subclassed QCustomPlot, that the overriding event method is being called.
Thinking about it I don't think that postEvent is the right thing to do here. The event that's being forwarded to QCustomPlot should be created on the stack and forwarded via sendEvent. Of course that won't fix the problem regarding the graph flickering while being dragged...

Yes, eventually mousePressEvent gets called, but I was looking for the whole chain of events.
Somewhere along the way to mousePressEvent there has to be resolved, what kind of event newEvent actually is and how to react to that.

From what I understand QCoreApplication::postEvent( m_CustomPlot, newEvent ) is supposed to send a "fake" mouse click/drag/whatever to the QCustomPlot widget.

At this point it gets the correct coordinates.
By the time these coordinates are passed to the actual method that repositions the plot on the screen these coordinates are sometimes wrong (multiplied by something).

I'm still in the dark why and where that happens... yet I am not the sharpest knife in the Qt drawer either :/

As far as I could see the coordinates that are passed to QCustomPlot make sense, at least I didn't see any faulty ones that could cause the jumping. My wild guess would be that QCustomPlot tries to reposition the graph while drawing and that results in conflicts when user is in the middle of dragging the graph around.
Maybe we could get another comment from DerManu regarding this issue?

Could anyone please verify the following:

If i set the width and height of the CustomPlotItem to 640x480 (in qml) zoom and drag works fine.

You are right Andy,
using

width: 640;  height: 480
works fine!
I tried other values for width and height with same ratio, but only 640x480 is fine

@modjo: If I use a QQuickItem as the base class I can create a QML interface to QCustomPlot and plot some data from a .qml file.
I'm trying to change the base class from QQuickPaintedItem to QQuickItem and use QQuickPaintedItem::updatePaintNode() instead of QQuickItem::paint() but the application crashes and I have trouble identifying the problem.

Here's my main.cpp:

#include <QApplication>
#include <QQmlApplicationEngine>

#include "QuickPlotSgs.hpp"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    qmlRegisterType<QuickPlotSgs>("QuickPlotSgs", 0, 1, "QuickPlotSgs");
    QQmlApplicationEngine engine;
    engine.addImportPath(":/.");
    engine.load(QUrl("qrc:///Example.qml"));
    return app.exec();
}

and the Example.qml is:
import QtQuick 2.5
import QtQuick.Controls 1.0 as QtQuickControls

QtQuickControls.ApplicationWindow {
    id: mainWindow
    visible: true
    width: 800
    height: 800

    QuickPlotSgs {
        id: plot
        anchors.fill: parent

        Timer {
            interval: 500; running: true; repeat: true
            onTriggered: {
                var newX = [];
                var newY = [];
                for (var i = 0; i < 20; i++) {
                    newX[i] = i;
                    newY[i] = Math.random();
                }
                plot.setData(newX, newY);
            }
        }

        Component.onCompleted: {
            plot.setData([1,2,3,4,5,6], [1,2,1,2,3,2])
        }
    }
}

A gdb backtrack doesn't help me here as it reports the problem in the app.exec() line.

Could it be caused by how the QApplication is created? If I comment out everything in QuickPlotSgs::updatePaintNode() and slowly decomment the code crash happens as soon as I create the temporary window (QQuickWindow tempWindow;). Could it be that the when the object goes out of scope and is destroyed, the main window gets destroyed instead?

Or maybe it's a problem due to using a QtQuickControls.ApplicationWindow?

How are you initializing your QML window?

Any one else with a suggestion?

Thanks!

I was able to adapt updatePaintNode() so it does not crash anymore:

QSGNode *QuickPlotSgs::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {
    QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>(oldNode);
    if (!node) {
        node = new QSGSimpleTextureNode();
    }
    if (m_CustomPlot) {
        QSGTexture *texture = window()->createTextureFromImage(m_CustomPlot->toPixmap().toImage());
        node->setTexture(texture);
        texture->deleteLater();
    }
    node->setRect(boundingRect());
    return node;
}

Note that I do not create a temporary window like modjo suggested. Also, the texture needs to be deleted or else you'll have a memory leak (see http://doc.qt.io/qt-5/qquickwindow.html#createTextureFromImage ).

Unfortunately, it was not faster than using QQuickPaintedItem. It seemed to have been slower even. And it was unstable. I could not reliably add a MouseArea over the plot area to control zooming (zooming in my case is purely controled in QML).

So in the end I reverted back to using QQuickPaintedItem.

Hi DerManu, are you considering a full/partial QML support provided by default in future versions of QCustomPlot? Thanks!

Marco

Hi,
thanks to Andy's observation (640x480) I figured out the problem.

Please add the setViewport call in updateCustomPlotSize() in the code I posted earlier, so that it looks like:

void CustomPlotItem::updateCustomPlotSize()
{
    if (m_CustomPlot)
    {
        m_CustomPlot->setGeometry(0, 0, (int)width(), (int)height());
        m_CustomPlot->setViewport(QRect(0, 0, (int)width(), (int)height()));
    }
}


Without this explicit setViewportcall, the "default" viewport set in the QCustomPlot::QCustomPlot constructor (to 640x480) is used and this default viewport is restored after QCustomPlot::toPainter calls. In consequence the QCustomPlot::replot function uses this wrong viewport and causes the flickering.

Thanks seven, it works like a charm!

For those arriving here, I recently tried the above code samples with success, however I had to slightly modify the updatePaintNode.

It was crashing as texture was being deleted (the deleteLater() call).

Changed the texture to a member variable.

Qt 5.8
QApplication
Base class QQuickItem
QCustomPlot Version 2.0.0-beta

QSGNode* CustomPlotItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
	QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>(oldNode);
	if (!node) {
		node = new QSGSimpleTextureNode();
	}
	if (m_CustomPlot) {
		if(m_texture)
		{
			delete m_texture; //delete in destructor
			m_texture = nullptr; 
		}
		m_texture = window()->createTextureFromImage(m_CustomPlot->toPixmap().toImage());
		node->setTexture(m_texture);
	}
	node->setRect(boundingRect());
	return node;
}

Hi all, and thank you for the info in this thread. Many thanks to Michael for providing the above answer. It helped a lot.
Also if you wish to use the initial code and subclass QQuickPaintedItem, I noticed a call to this->setRenderTarget(QQuickPaintedItem::FramebufferObject); in the paint() method helped performance a lot in my case.
Regards!

Hi, all
How can i use QCustomPlot in qml?

Hi, I've implemented solution of the Seven and provide tiny test example.
Here's the link: https://github.com/mosolovsa/qmlplot

Hope it'll be helpfull, good luck!