Selecting QLineSeries in a QChartView using rubberband selection

1.3k views Asked by At

I have a QChart with a number of QLineSeries on it.

I have created my own chart class CPlotView derifed from QChartView and overridden the mousePressEvent, mouseMoveEvent and mouseReleaseEvent as shown below.

I want to implement both rubberband zoom and rubberband select separately, and I can choose which to do based on checkable QPushButtons (code not shown but its just a member of CPlotView which holds an enum value which defined what 'mode' its in (Zoom or Select)

void CPlotView::mousePressEvent(QMouseEvent *event)
{
     if(PlotRubberBand)
     {
         if (PlotCursorMode == ECursorMode::eIsolate || PlotCursorMode == ECursorMode::eZoom)
         {
             QRectF plotArea = chart()->plotArea();
             if (PlotRubberBand->isEnabled()
                 && event->button() == Qt::LeftButton
                 && plotArea.contains(event->pos()))
             {

                 DrawRubberBand = true;
                 PlotRubberBandOrigin = event->pos();
                 PlotRubberBand->setVisible(true);
                 PlotRubberBand->setGeometry(QRect(PlotRubberBandOrigin, QSize()));
                 event->accept();
             }
             QGraphicsView::mousePressEvent(event);

         }
         else
         {
             QChartView::mousePressEvent(event);
         }
     }

    QChartView::mousePressEvent(event);
}

void CPlotView::mouseMoveEvent(QMouseEvent *event)
{
    if (GetPlotCursorMode() == ECursorMode::eIsolate)
    {
        if (DrawRubberBand && PlotRubberBand->isVisible())
        {
            QRect rect = chart()->plotArea().toRect();
            int width = event->pos().x() - PlotRubberBandOrigin.x();
            int height = event->pos().y() - PlotRubberBandOrigin.y();

            PlotRubberBand->setGeometry(QRect(PlotRubberBandOrigin.x(), PlotRubberBandOrigin.y(), width, height).normalized());

        }
        else
        {
             QGraphicsView::mouseMoveEvent(event);
        }
    }
    else if (GetPlotCursorMode() == ECursorMode::eZoom)
    {
        if (DrawRubberBand && PlotRubberBand->isVisible())
        {
            QChartView::mouseMoveEvent(event);
        }
        else
        {
            QGraphicsView::mouseMoveEvent(event);
        }
    }
    else if (GetPlotCursorMode() == ECursorMode::eSelect)
    {
        QGraphicsView::mouseMoveEvent(event);
    }
    else
    {
        QChartView::mouseMoveEvent(event);

    }

}

void CPlotView::mouseReleaseEvent(QMouseEvent *event)
{
    if (PlotRubberBand->isVisible())
    {
        if (PlotCursorMode == ECursorMode::eIsolate)
        {
             DrawRubberBand = false;
             PlotRubberBand->setVisible(false);

            QGraphicsView::mouseReleaseEvent(event);

            QList<QGraphicsItem *> selected_items = QGraphicsView::items(PlotRubberBand->rect(), Qt::IntersectsItemShape);

            qDebug() << "found " << selected_items.size() << " items"; // this appears to show a number of items, but how do I detect which items are my data series, if any are?
            for (auto& item : selected_items)
            {
                // detect which are my data series and highlight them as selected
            }

        }
        else
        {
            DrawRubberBand = false;
            PlotRubberBand->setVisible(false);
            QChartView::mouseReleaseEvent(event);
        }
    }
}

I have now got the zoom mode working - clicking the 'zoom' mode button then dragging the rubberband on the plot zooms into that area. Clicking the select mode button draws the rubberband but doesn't zoom. I want to now select all data series in the rubberband in select mode.

In mouseReleaseEvent in select mode, I am attempting to capture all selected QGraphicsItems inside the rubberband rect (I'm hoping that is what is returned by QGraphicsView::items(PlotRubberBand->rect(), Qt::IntersectsItemShape);), but I don't know how to detect my data series (I actually have a mixture of QLineSeries and QScatterSeries.

There are some QGraphicsItems being returned by QGraphicsView::items(PlotRubberBand->rect(), Qt::IntersectsItemShape); even when I don't intersect the rubberband with any of my data so it is obviously returning some other items in the scene.

I tried doing a dynamic_cast of what is returned by QGraphicsView::items to a QLineSeries, but that didn't work. I can't help but think I'm making this more complicated than it should be.

UPDATE1: I have also tried capturing the signals fired when the mouse hovers over the data series during the rubberband select using:

connect(line_series, SIGNAL(hovered(const QPointF &, bool)), this, SLOT(OnDataHovered(const QPointF&, bool)));

but the signal doesn't fire when rubberband dragging (perhaps when left mouse button is down it doesn't fire?). :(

UPDATE 2: I have tried setting the selectionArea in the mouseReleaseEvent() call, and again, some this are returned as selected, far fewer than calling QGraphicsView::items(), but not my series as far as I can tell:

void ewbearinghistorychart::CEwBearingHistoryChartView::mouseReleaseEvent(QMouseEvent *event)
{
    if (PlotRubberBand->isVisible())
    {
        if (PlotCursorMode == ECursorMode::eIsolate)
        {
            DrawRubberBand = false;
            PlotRubberBand->setVisible(false);
            QRect rubberband_rect = PlotRubberBand->rect();
            QGraphicsView::mouseReleaseEvent(event);

            // ************** new code **************
            QPolygonF p = chart()->mapToScene(PlotRubberBand->rect().normalized());
            QPainterPath path;
            path.addPolygon(p);
            chart()->scene()->setSelectionArea(path, Qt::IntersectsItemShape);
            QList<QGraphicsItem *> selected_items = chart()->scene()->selectedItems();
            qDebug() << "selected items: " << selected_items.size();
            // ************** /new code **************                
        }
        else
        {
            DrawRubberBand = false;
            PlotRubberBand->setVisible(false);
            QChartView::mouseReleaseEvent(event);
        }
    }
}
2

There are 2 answers

0
francek On

"I tried doing a dynamic_cast of what is returned by QGraphicsView::items to a QLineSeries, but that didn't work."

Here is how I get the QGraphicsItem representation of a QLineSeries.

QChart        ch;
QLineSeries  srs;

QList<QGraphicsItem*> bf{ch.childItems()};
ch.addSeries(&srs);
QList<QGraphicsItem*> af{ch.childItems()};

QGraphicsItem * mf;

for(auto e: af)
    if(!bf.contains(e))
        mf = e;

qDebug().operator<<(mf->isUnderMouse());
0
Mitch On

The problem you are having is that when rubber band mode is enabled QChartView re-implements the MousePressEvent and accepts the event. Since events only propagate upwards the QLineSeries (child of QChart and QChartView) never gets the memo.

The solution is to install a QEventFilter so that you can intercept the event before QChartView accepts it; however, I have not figured out how to detect if it was actually the QLineSeries that was clicked.