iwbnwif / wxFreeChart

This is a clone (not fork) of the pbfordev/wxFreeChart repository. It has their original fixes to the the wxCode version.
Other
37 stars 21 forks source link

Make charts interactive #18

Open iwbnwif opened 7 years ago

iwbnwif commented 7 years ago

There has been some work on making charts interactive, but this needs to be completed. The minimum requirement is to show a tooltip with the precise values when the user hovers the mouse over a specific data point.

psychegr commented 7 years ago

I see that you have changed the mousevents handling in the code. Do you have a code example to enable tooltips? I would like to give it a go with changing the points on the plot using the mouse.

iwbnwif commented 7 years ago

I had a very simple version working, but it is disable for now due to the change in the dataset structure.

Look at the bottom of axisplot.cpp and you will see the code I had in before. You simply need to override OnMouseMove in any class derived from Plot to start getting mouse events (hopefully!).

My code did a brute force search of all datasets to see if any datapoint is near the mouse cursor. As soon as it found one that was within a threshold, the handler called SetTipData with the text to be displayed.

psychegr commented 7 years ago

Following your old method, i adapted it to work with the new dataset. Here is the new code :

void XYPlot::OnMouseMove(wxMouseEvent& event)
{
    for (size_t set = 0; set < GetDatasetCount(); set++)
    {
        BiDataSet* dataset = wxDynamicCast(GetDataset(set), BiDataSet);

        NumberAxis* xAxis = static_cast<NumberAxis*>(GetDatasetAxis(dataset, false));
        NumberAxis* yAxis = static_cast<NumberAxis*>(GetDatasetAxis(dataset, true));

        for (size_t ser = 0; ser < dataset->GetSeriesCount(); ser++)
        {
            DataSeries* series = dataset->GetSeries(ser).get();

            wxMemoryDC dummy;

            double x = xAxis->ToData(dummy, m_rect.x, m_rect.GetWidth(), event.GetPosition().x);
            double y = yAxis->ToData(dummy, m_rect.y, m_rect.GetHeight(), event.GetPosition().y);

            for (size_t pt = 0; pt < series->GetSize(); pt++)
            {
                BiDataPoint* point = wxDynamicCast(series->GetPoint(pt).get(), BiDataPoint);
                double fi = point->first.As<double>();
                double se =  point->second.As<double>();
                if (fi < x + 5 && fi > x - 5 &&
                    se < y + 5 && se > y - 5)
                {
                    wxLogMessage("Got a point in Dataset: %d, Series: %d, Point Index: %d, at Point (x,y): %g, %g",set, ser, pt, fi, se);

            SetTipData(wxString::Format("x : %g\ny : %g", fi, se));
            return;
                }
            }
        }
    }
}

It seems to work quite nice, but it seems that there is an "offset" on the points that the tooltips are displayed. Maybe it is that the +5, -5 values are too high? I will do some more tests and report back. Also the when moving the mouse around the tooltip flickers. Maybe it has to do with the wxMemoryDC or something.

psychegr commented 7 years ago

Working a little bit more on the "interactivity" here is a code snippet that i use to drag points. I am using it in the OnMouseMove event and i check if the event.Dragging is true.

if (event.Dragging())
{
    for (size_t set = 0; set < GetDatasetCount(); set++)
    {
        BiDataSet* dataset = wxDynamicCast(GetDataset(set), BiDataSet);

        NumberAxis* xAxis = static_cast<NumberAxis*>(GetDatasetAxis(dataset, false));
        NumberAxis* yAxis = static_cast<NumberAxis*>(GetDatasetAxis(dataset, true));

        for (size_t ser = 0; ser < dataset->GetSeriesCount(); ser++)
        {
            DataSeries* series = dataset->GetSeries(ser).get();

            wxMemoryDC dummy;

            double x = xAxis->ToData(dummy, m_rect.x, m_rect.GetWidth(), event.GetPosition().x);
            double y = yAxis->ToData(dummy, m_rect.y, m_rect.GetHeight(), event.GetPosition().y);

            for (size_t pt = 0; pt < series->GetSize(); pt++)
            {
                BiDataPoint* point = wxDynamicCast(series->GetPoint(pt).get(), BiDataPoint);
                double fi = point->first.As<double>();
                double se = point->second.As<double>();

                // drag a point vertically
                // maybe add a couple flags to enable dragging horizontaly or vertically or both
                if ((fi > x - 0.5) && (fi < x + 0.5))
                {
                    point->SetValues(fi, y);
                    dataset->DatasetChanged();
                    return;
                }
            }
        }
    }
}

I hope that this will help enough to properly implement a "dragging" feature.

iwbnwif commented 7 years ago

Hi, I think that this is okay, but it would be better to somehow capture the point once dragging has started otherwise if you drag the point near another point it may jump and start dragging the other point instead. Also, this would avoid searching all the datasets on every mouse move.

Therefore, I would suggest using both OnMouseDown (not yet implemented!!) and OnMouseMove in future.