QCustomPlot Discussion and Comments

How to get the point under the mouse in a graphReturn to overview

I hope this code helps others out, you should attach this slot to the plottable clicked signal provided by QCustomPlot. It will display a tool tip as long as the user holds the right mouse button down. I don't recommend using this function on a plot with hundreds of thousands of data points in a single graph because of the method by which it detects which point is the closest. A binary search can be used for the bar and regular graph plottables (because they have sorted data) but a linear search has to be used for curves... and curves have the most expensive search function.

void plotMousePress(QMouseEvent *event)
{
    if(event->button() == Qt::RightButton)
    {
        QCPAbstractPlottable *plottable =
        m_ui->plot->plottableAt(event->posF());

        if(plottable)
        {
            double x = m_ui->plot->xAxis->pixelToCoord(event->posF().x());
            double y = m_ui->plot->yAxis->pixelToCoord(event->posF().y());

            QCPBars *bar =
            qobject_cast<QCPBars*>(plottable);

            if(bar)
            {
                double key = 0;
                double value = 0;

                bool ok = false;
                double m = std::numeric_limits<double>::max();

                foreach(QCPBarData data, bar->data()->values())
                {
                    double d = qAbs(x - data.key);

                    if(d < m)
                    {
                        key = data.key;
                        value = data.value;

                        ok = true;
                        m = d;
                    }
                }

                if(ok)
                {
                    for(QCPBars *below = bar->barBelow();
                    ((below != NULL) && below->data()->contains(key));
                    below = below->barBelow())
                    {
                        value += below->data()->value(key).value;
                    }

                    QToolTip::hideText();
                    QToolTip::showText(event->globalPos(),
                    tr("<table>"
                         "<tr>"
                           "<th colspan=\"2\">%L1</th>"
                         "</tr>"
                         "<tr>"
                           "<td>Key:</td>" "<td>%L2</td>"
                         "</tr>"
                         "<tr>"
                           "<td>Val:</td>" "<td>%L3</td>"
                         "</tr>"
                       "</table>").
                       arg(bar->name().isEmpty() ? "..." : bar->name()).
                       arg(key).
                       arg(value),
                       m_ui->plot, m_ui->plot->rect());
                }
            }

            QCPCurve *curve =
            qobject_cast<QCPCurve*>(plottable);

            if(curve)
            {
                double key = 0;
                double value = 0;

                bool ok = false;
                double m = std::numeric_limits<double>::max();

                foreach(QCPCurveData data, curve->data()->values())
                {
                    double d = qSqrt(qPow(x-data.key,2)+qPow(y-data.value,2));

                    if(d < m)
                    {
                        key = data.key;
                        value = data.value;

                        ok = true;
                        m = d;
                    }
                }

                if(ok)
                {
                    QToolTip::hideText();
                    QToolTip::showText(event->globalPos(),
                    tr("<table>"
                         "<tr>"
                           "<th colspan=\"2\">%L1</th>"
                         "</tr>"
                         "<tr>"
                           "<td>X:</td>" "<td>%L2</td>"
                         "</tr>"
                         "<tr>"
                           "<td>Y:</td>" "<td>%L3</td>"
                         "</tr>"
                       "</table>").
                       arg(curve->name().isEmpty() ? "..." : curve->name()).
                       arg(key).
                       arg(value),
                       m_ui->plot, m_ui->plot->rect());
                }
            }

            QCPGraph *graph =
            qobject_cast<QCPGraph*>(plottable);

            if(graph)
            {
                double key = 0;
                double value = 0;

                bool ok = false;
                double m = std::numeric_limits<double>::max();

                foreach(QCPData data, graph->data()->values())
                {
                    double d = qAbs(x - data.key);

                    if(d < m)
                    {
                        key = data.key;
                        value = data.value;

                        ok = true;
                        m = d;
                    }
                }

                if(ok)
                {
                    QToolTip::hideText();
                    QToolTip::showText(event->globalPos(),
                    tr("<table>"
                         "<tr>"
                           "<th colspan=\"2\">%L1</th>"
                         "</tr>"
                         "<tr>"
                           "<td>X:</td>" "<td>%L2</td>"
                         "</tr>"
                         "<tr>"
                           "<td>Y:</td>" "<td>%L3</td>"
                         "</tr>"
                       "</table>").
                       arg(graph->name().isEmpty() ? "..." : graph->name()).
                       arg(key).
                       arg(value),
                       m_ui->plot, m_ui->plot->rect());
                }
            }

            QCPStatisticalBox *box =
            qobject_cast<QCPStatisticalBox*>(plottable);

            if(box)
            {
                double key = box->key();
                double minimum = box->minimum();
                double lowerQuartile = box->lowerQuartile();
                double median = box->median();
                double upperQuartile = box->upperQuartile();
                double maximum = box->maximum();

                QToolTip::hideText();
                QToolTip::showText(event->globalPos(),
                tr("<table>"
                     "<tr>"
                       "<th colspan=\"2\">%L1</th>"
                     "</tr>"
                     "<tr>"
                       "<td>Key:</td>" "<td>%L2</td>"
                     "</tr>"
                     "<tr>"
                       "<td>Min:</td>" "<td>%L3</td>"
                     "</tr>"
                     "<tr>"
                       "<td>L-Q:</td>" "<td>%L4</td>"
                     "</tr>"
                     "<tr>"
                       "<td>Mid:</td>" "<td>%L5</td>"
                     "</tr>"
                     "<tr>"
                       "<td>U-Q:</td>" "<td>%L6</td>"
                     "</tr>"
                     "<tr>"
                       "<td>Max:</td>" "<td>%L7</td>"
                     "</tr>"
                   "</table>").
                   arg(box->name().isEmpty() ? "..." : box->name()).
                   arg(key).
                   arg(minimum).
                   arg(lowerQuartile).
                   arg(median).
                   arg(upperQuartile).
                   arg(maximum),
                   m_ui->plot, m_ui->plot->rect());
            }
        }
    }
}

Thanks, Kwabena! I'm sure many people will find this helpful.

And it also tells me, that QCustomPlot should improve in that direction, to make this alot easier and spare the user all the fiddly work :)

This code pretty much gives you everything you need. Just re-implement the QToolTip Event in CustomPlot and run this code. Do a B-Search on the sorted data plottables... and you'e good. Also, provide a call back function for people who have custom plottables.

However, I still don't know how to get the tool tip to stay without having the hold the right mouse button down. But, the above is a good start.

Thanks,

Emanuel, can't you use the signal/slot system on drawn items, like curves? I thought you could make it so, when the mouse enters a particular curve it signals it; a user might just catch that signal. It would be the most inexpensive way since you don't have to test-point all the data.

Just wanted to say thanks for the code!

A note to people using Qt 5+, posF() has been replaced with localPos(). Other than that I dropped the code in and had it working instantly.

When the mouse is away from the curve, I get incorrect X and Y. Following condition will correct it.

if(plottable)
{
   ....  
}
else
   QToolTip::hideText();

Thanks Kwabena your code helps me alot.

Thanks a lot for this. But I have only question: The signal is:

void QCustomPlot::plottableClick ( QCPAbstractPlottable * plottable, QMouseEvent * event )	

so I had to modify a little your method in:
void plotMousePress(QCPAbstractPlottable * plot, QMouseEvent *event)
, even though the "plot" parameter is never used. If i want to use the method exactlly how you read here, i get some compilation error that said it's a problem between the Signal and Slot. Signal expects 2 parameters, but the slot has only one.

Thank you a lot,
Manich.

Thanks Kwabena for posting this code and DerManu for this great library.

I used this code as below. It worked well until I moved to QCustomPlot 2.0.0-beta. The version 2.0.0 is not supporting graph->data()->values() and QCPData in the foreach loop. I changed QCPData to QCPGraphData, but I still could not figure out how to access graph->data values. I don't want to go back and use Version 1.3.2. Could you suggest an alternative approach access graph data?

connect(ui->plotG, SIGNAL(mouseMove(QMouseEvent*)), this,SLOT(on_mouseOverPlotHeight(QMouseEvent*)));

void MainWindow::MouseOverPlotHeight(QMouseEvent *event)
{
    QCustomPlot *curPlot = ui->customPlot_Height;
    QString strNameX = "Days";
    QString strNameY = "Height";
    DisplayData(event, curPlot, strNameX, strNameY);
}

void MainWindow::DisplayData(QMouseEvent *event, QCustomPlot *curPlot, QString strNameX, QString strNameY)
{
    QCPAbstractPlottable *plottable = curPlot->plottableAt(event->localPos());
    if(plottable)
    {
        double x = curPlot->xAxis->pixelToCoord(event->localPos().x());
        double y = curPlot->yAxis->pixelToCoord(event->localPos().y());
        QCPGraph *graph =  qobject_cast<QCPGraph*>(plottable);if(graph)
        {
            double key = 0;
            double value = 0;
            bool ok = false;
            double m = std::numeric_limits<double>::max();
            foreach(QCPData data, graph->data()->values())
            {
                double d = sqrt((x - data.key)*(x - data.key) + (y - data.value)*(y - data.value));
                if(d < m)
                {
                    key = data.key;
                    value = data.value;
                    ok = true;
                    m = d;
                }
            }
            if(ok)
            {
                QToolTip::showText(event->globalPos(),
                tr("<table>"
                     "<tr>"
                       "<td>%L1:</td>" "<td>%L2</td>"
                     "</tr>"
                     "<tr>"
                       "<td>%L3:</td>" "<td>%L4</td>"
                     "</tr>"
                   "</table>").arg(strNameX).arg(key).arg(strNameY).arg(value),curPlot, curPlot->rect());
            }
        }
    }
    else
        QToolTip::hideText();
}

Could someone suggest me how change following foreach loop to access data values.

QCPGraph *graph =  qobject_cast<QCPGraph*>(plottable);
foreach(QCPData data, graph->data()->values())
{
       ........
}

I am using QCustomPlot 2.0.0-beta
Thanks