QCustomPlot Discussion and Comments

Ability to add and also select and move a point on the plot itselfReturn to overview


Is it possible via the interaction methods to allow for selection of a point on an already plotted graph and move that
points y or x axis value using a drag and drop mouse click action. Then after all changes are done to retrieve the new data from the plot?

Sort of backwards from plotting from the data. The idea is to click and add points to the chart, interconnect the points together left to right and then save that data off for use later after one gets the plot looking like they want it to look.

Thank you...

Curtis Rubel
crubel@compro.net

I'm also interested in having an answer to that question.
Is it possible to drag a single point of a curve, drawing the new curve on release?

This interests me greatly. It's basically the only thing that I am not sure whether qcustomplot can perform. Can someone answer this please?

I need to do something like that, but I can not find how to do it.

Here is a subclass that alows you to drag single points of one graph and also add new points if the mouse hits an empty space (you have to set a margin of how many pixel away from a point a new one should be created):

The Header:

#ifndef EQWIDGET_H
#define EQWIDGET_H

#include "qcustomplot.h"


class EQWidget : public QCustomPlot
{
    Q_OBJECT
public:
    EQWidget(QWidget* parent);
    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void setDragInfo( int graph = -1, int distance = -1);
signals:
    void DataChanged( void );
    void EditingFinished(void);
private:
    int drag_number = -1;
    int dragable_graph_number = -1;
    int max_distance_to_add_point = -1;
};

#endif // EQWIDGET_H

The .cpp:

#include "eqwidget.h"

EQWidget::EQWidget(QWidget* parent) : QCustomPlot(parent)
{

}

void EQWidget::setDragInfo(int graph, int distance)
{
    dragable_graph_number = graph;
    max_distance_to_add_point = distance;
}

void EQWidget::mousePressEvent(QMouseEvent *event)
{
    double x,y;

    double best_dist = 1E+300;
    int best_index = 0;
    if( (dragable_graph_number >= 0) && ( dragable_graph_number < this->graphCount()) )
    {
        QCPGraph* pq_graph = this->graph(dragable_graph_number);
        pq_graph->pixelsToCoords(event->localPos(),x,y);
        if( pq_graph->data()->size() >= 1 )
        {
            for( int n=0; n<(pq_graph->data()->size()); n++ )
            {
                double dist = fabs( (pq_graph->data()->begin()+n)->value - y );
                dist += fabs( (pq_graph->data()->begin()+n)->key - x );
                if( dist < best_dist )
                {
                    best_dist = dist;
                    best_index = n;
                }
            }
            if( max_distance_to_add_point > 0 )
            {
                QPointF q_pos_gui = pq_graph->coordsToPixels( (pq_graph->data()->begin()+best_index)->key, (pq_graph->data()->begin()+best_index)->value );
                int dist_px = fabs( event->localPos().x() - q_pos_gui.x()) +  fabs( event->localPos().y() - q_pos_gui.y());
                if( dist_px/2 > max_distance_to_add_point )
                {
                    pq_graph->addData(x,y);
                    pq_graph->data().data()->sort();
                    for( int n=0; n<(pq_graph->data()->size()); n++ )
                    {
                        if(  (pq_graph->data()->begin()+n)->value == y && (pq_graph->data()->begin()+n)->key == x ) best_index = n;
                    }
                }
            }
            drag_number = best_index;
        }
    }
    QCustomPlot::mousePressEvent(event);
}

void EQWidget::mouseReleaseEvent(QMouseEvent *event)
{
    drag_number = -1;
    if( (dragable_graph_number >= 0) && ( dragable_graph_number < this->graphCount()) )
    {
        this->graph(dragable_graph_number)->data().data()->sort();
    }
    this->replot();
    emit( EditingFinished() );
    QCustomPlot::mouseReleaseEvent(event);
}

void EQWidget::mouseMoveEvent(QMouseEvent *event)
{
    double x,y;

    if( (dragable_graph_number >= 0) && ( dragable_graph_number < this->graphCount()) )
    {
        QCPGraph* pq_graph = this->graph(dragable_graph_number);
        pq_graph->pixelsToCoords(event->localPos(),x,y);

        y = round( y*16 ) / 16; //snap to grid 
        x = round( x*4 ) / 4; //snap to grid

        if( drag_number >= 0 )
        {
            (pq_graph->data()->begin()+drag_number)->value = y;
            (pq_graph->data()->begin()+drag_number)->key = x;
            emit( DataChanged() );
            this->replot();
        }
    }
}

usage:

     ui->eqplot->addGraph(); 
     ui->eqplot->setDragInfo(0, 20); // data of graph(0) is dragable, 20px margin
     connect(ui->eqplot,&EQWidget::DataChanged,this,&MainWindow::RebuildResultGraph);

DataChanged(void) and EditingFinished(void) are the signals you can use to react on the user interaction.

you can modify the code to meet you requirments..

Hi BeFa,
first of all, thanks for posting your code!
One question: Is there any particular reason why you didn't use the selectTest method? This saves you all the trouble finding the right data point from the x/y pixel point.
e.g. like this:

 QVariant details;
if (graph->selectTest(QPoint(x, y), false, &details))
{
  drag_number = details.value<QCPDataSelection>().dataRange().begin();
}
 

Hi Manu,
I tried that at first but with no success. Maybe I used it the wrong way.
What does the expression details.value<QCPDataSelection>().dataRange().begin(); do? Will it return the index of the data point in the graph->data().data() vector? ... and why?