Retrieving vtkChartXY data from mouse click

I’m trying to get the selected data point from where a user clicked on the chart with a mouse.
I manage to get the mouse click event with the following code:

vtkSmartPointer <vtkCallbackCommand> callback = vtkSmartPointer <vtkCallbackCommand>::New();
callback->SetCallback(HandleChartXYMousePressEvent);
this->pvtkMFCWindowChart->GetInteractor()->AddObserver(vtkCommand::LeftButtonPressEvent, callback);

And then in the HandleChartXYMousePressEvent function I got the Chart object by doing this rather elaborate coding:

vtkRenderWindowInteractor* iren = vtkRenderWindowInteractor::SafeDownCast(obj);
int pos[2] = { 0 };
Iren->GetEventPosition(pos);
vtkRenderer* pvtkRender = iren->FindPokedRenderer(pos[0], pos[1]);
vtkPropCollection* pPropCol = pvtkRender->GetViewProps();
pPropCol->InitTraversal();
vtkContextActor* pConAct = vtkContextActor::SafeDownCast(pPropCol->GetNextProp());
vtkContextScene* pScene = pConAct->GetScene();
int nScenes = pScene->GetNumberOfItems();
vtkChartXY* pChartXY = vtkChartXY::SafeDownCast(pScene->GetItem(0));

(The above was simplified with removed error checking)

The problem is that I tried everything to get the data from the Chart. Can anybody point me in the right direction?

Hi, Daniel,

VTK charts are visually great, but I found that API a bit cumbersome when it comes to interactive charts. Interactive in the sense that you can pick individual curves, drag points, etc. I resorted to Qt Charts and qwt that made my life a lot easier. Anyway, after some googling I found this: https://echnotstechno.wordpress.com/2014/08/13/interactive-2d-plots-with-vtk-the-visualization-toolkit/ . It is from 2014, but it may put to you in the right track.

regards,

Paulo

1 Like

I also agree with Paolo. There’s also QCustomPlot (https://www.qcustomplot.com/) which is more easier to use than Qwt but it has a GPL license (it’s only an issue if you intend to use it in a commercial product). Qwt is fine, and that’s what I’m using at my current work but you will need to code Qt classes (event filters) to handle certain things more finely. Whereas, IMHO, QCP provides you with the most common features, but the quality of its code/internal degisn is lower than that of Qwt (QCP consists only of a .h and a huge cpp file).

1 Like

Yes, the GPL license is an imporant detail if you opt to use Qt Charts.

Thanks Paulo, I will need to work through the link you gave me and let you know if it solves my problem. It is definitely different that what I was expecting.

Daniel,

Have you tried subclassing vtkChartXY? This class has mouse events such as MouseButtonPressEvent. I think you are better off subclassing it and overriding the mouse events instead of the hack you are doing in an external callback. Inside vtkChartXY events, you have seamless access to the collection of vtkPlot's and, thence, to the data.

regards,

Paulo

Paulo,

Thanks, but the MouseButtonPressEvent never gets called.

Regards,
Daniel

Maybe your code needs to forward the event to the other handler codes down in the event handler chain. That can explain why MouseButtonPressEvent is not being called. The protocol is generally to call the superclass’ version of the event handling function like in the example below:

void v3dMouseInteractor::OnLeftButtonDown()
{
    m_isLBdown = true;

    // Forward the event to the superclass.
    vtkInteractorStyleTrackballCamera::OnLeftButtonDown();
}

If the handler expects parameters, you need to pass along what you received.

I’m beginning to wonder if it is an issue with my setup or perhaps that I’m using vtkMFCWindow.

I manage to track down a message from some other VTK forum which said that they managed to get the left click events with the following code:

void pointSelected(vtkObject* obj, unsigned long,void*, void*)
{
    std::cout << "\nPoint selected.\n";
    // Print info of selected point
}

int main()
{
   chart->SetActionToButton(vtkChartXY::SELECT, vtkContextMouseEvent::LEFT_BUTTON);

   vtkSmartPointer<vtkCallbackCommand> keypressCallback = vtkSmartPointer<vtkCallbackCommand>::New();
   keypressCallback->SetCallback ( pointSelected );
   chart->AddObserver(vtkContextMouseEvent::LEFT_BUTTON, keypressCallback);
}

I tried everything but could not get it going.
.

Hmmm… These calls are too low level (e.g. AddObserver is defined in vtkObject):

   vtkSmartPointer<vtkCallbackCommand> keypressCallback = vtkSmartPointer<vtkCallbackCommand>::New();
   keypressCallback->SetCallback ( pointSelected );
   chart->AddObserver(vtkContextMouseEvent::LEFT_BUTTON, keypressCallback);

I wouldn’t use them.

Now, for your problem. First, take a look at the methods of vtkPlot that contains Selection in the name that configure how selection will work (e.g. which mouse button to use, whether it’ll be by columns or rows, etc.). There are many of them. Pick the ones that suit you: https://vtk.org/doc/nightly/html/classvtkChart.html . These methods only affect how selection will work.

Now, to the problem of getting feedback. I suggest using vtkChart::setAnnotationLink(). This, of course, requires creating a vtkAnnotationLink object: https://vtk.org/doc/nightly/html/classvtkAnnotationLink.html . Then, I’d take a look at vtkAnnotationLink::Set/GetCurrentSelection(). This post may also cast light in the mechanics of how to interact with VTK charts: http://vtk.1045678.n5.nabble.com/Selecting-Points-in-a-Plot-Rows-in-a-table-td5724213.html .

OK. I found part of my problem. I know why I did not get any mouse click events. I was using vtkMFCWindow and tried to link the vtkContextView with it. As far as I can tell, you are not supposed to link the context view with the MFC window (or I don’t know how to properly). I have now added my own vtkContextActor and from that, I retrieved the scenes needed for the vtkChart.

So I’m getting the mouse events now and there is actually a tooltip that was not there previously. I’m getting data point values with every second mouse click at the moment. I will try to follow your (Paulo) suggestions next week to see if I can get the datapoint with every click.

Hi, Daniel, unfortunatelly I can’t go much further since I don’t use MFC (I work with Qt). I can only assist with overall concepts of GUIs.

No that I properly implemented the vtkChartXY it all seems easy now. Cannot believe I wasted a week on it. Anyway, for those that might have the same issue, here is some extract from my code that will show you how to get the selected data from a mouse click event:

What this does is that it will place a selection market on the mouse clicked point for demonstration purposes. So the actual selected data point is captured in the plotPos variable after the call to pPlot->GetNearestPoint(position, tolerance, &plotPos).

The header:

class vtkMyChartXY :public vtkChartXY
{
public:
	vtkTypeMacro(vtkMyChartXY, vtkChartXY);
    /**
     * Creates a 2D Chart object.
     */
    static vtkMyChartXY* New();
private:
    bool MouseButtonPressEvent(const vtkContextMouseEvent& mouse) override;

    //Copied from vtkChartSelectionHelper.h
    void MakeSelection(vtkAnnotationLink* link, vtkIdTypeArray* selectionIds, vtkPlot* plot);
};

The cpp file:

vtkStandardNewMacro(vtkMyChartXY);

bool vtkMyChartXY::MouseButtonPressEvent(const vtkContextMouseEvent& mouse)
{
    size_t n = this->GetNumberOfPlots();
    vtkVector2i pos(mouse.GetScreenPos());
    if (pos[0] > this->Point1[0] && pos[0] < this->Point2[0] && pos[1] > this->Point1[1] &&
        pos[1] < this->Point2[1] && n)
    {
        //Iterate through plots
        for (size_t nPlot = 0; nPlot < n; ++nPlot) {
            vtkPlot* pPlot = this->GetPlot(nPlot);
            //Plot parent is the Plot corner (this->ChartPrivate->PlotCorners[i])
            vtkContextTransform* pPlotCorner = vtkContextTransform::SafeDownCast(pPlot->GetParent());
            if (pPlotCorner) {
                vtkVector2f plotPos, position;
                vtkTransform2D* pTransform = pPlotCorner->GetTransform();
                pTransform->InverseTransformPoints(mouse.GetPos().GetData(), position.GetData(), 1);
                // Use a tolerance of +/- 5 pixels
                vtkVector2f tolerance(std::fabs(5 * (1.0 / pTransform->GetMatrix()->GetElement(0, 0))),
                    std::fabs(5 * (1.0 / pTransform->GetMatrix()->GetElement(1, 1))));
                // Iterate through the visible plots and return on the first hit
                vtkIdType segmentIndex = -1;
                int seriesIndex = -1;
                if (pPlot && pPlot->GetVisible())
                {
                        seriesIndex =  pPlot->GetNearestPoint(position, tolerance, &plotPos);
                }
                  if (seriesIndex >= 0) {
                    // Construct a new selection with the selected point in it.
                    vtkNew<vtkIdTypeArray> selectionIds;
                    
                    selectionIds->InsertNextValue(seriesIndex);
                    pPlot->SetSelection(selectionIds);
                    break; //only one selection per plot
                }
            }
        }
    }
    
	return vtkChartXY::MouseButtonPressEvent(mouse);
}

/// <summary>
/// Copies from vtkChartSelectionHelper.h
/// </summary>
void vtkMyChartXY::MakeSelection(vtkAnnotationLink* link, vtkIdTypeArray* selectionIds, vtkPlot* plot)
{
    assert(link != nullptr && selectionIds != nullptr);

    if (plot)
    {
        // We are building up plot-based selections, using multiple nodes.
        vtkSelection* selection = link->GetCurrentSelection();
        vtkSmartPointer<vtkSelectionNode> node;
        for (unsigned int i = 0; i < selection->GetNumberOfNodes(); ++i)
        {
            vtkSelectionNode* tmp = selection->GetNode(i);
            vtkPlot* selectionPlot =
                vtkPlot::SafeDownCast(tmp->GetProperties()->Get(vtkSelectionNode::PROP()));
            if (selectionPlot == plot)
            {
                node = tmp;
                break;
            }
        }
        if (!node)
        {
            node = vtkSmartPointer<vtkSelectionNode>::New();
            selection->AddNode(node.GetPointer());
            node->SetContentType(vtkSelectionNode::INDICES);
            node->SetFieldType(vtkSelectionNode::POINT);
            node->GetProperties()->Set( vtkSelectionNode::PROP(), plot);
            node->GetProperties()->Set(vtkSelectionNode::SOURCE(), plot->GetInput());
        }
        node->SetSelectionList(selectionIds);
    }
    else
    {
        // Use a simple single selection node layout, remove previous selections.
        vtkNew<vtkSelection> selection;
        vtkNew<vtkSelectionNode> node;
        selection->AddNode(node);
        node->SetContentType(vtkSelectionNode::INDICES);
        node->SetFieldType(vtkSelectionNode::POINT);
        node->SetSelectionList(selectionIds);
        link->SetCurrentSelection(selection);
    }
}

Hopes this helps somebody in the future.

Regards,
Daniel

2 Likes

Daniel, overriding events is a quite common design in modern APIs that respond to user gestures. It’s troublesome, yes, but that’s the more design-friendly way to deal with GUI events. The others being callbacks and event filters. These may result in less coding, but they result in bad code design and, thus, prone to hideous bugs.