wxFreeChart icon indicating copy to clipboard operation
wxFreeChart copied to clipboard

Make charts interactive

Open iwbnwif opened this issue 7 years ago • 5 comments

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.

iwbnwif avatar Apr 18 '17 11:04 iwbnwif

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.

psychegr avatar May 27 '17 17:05 psychegr

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.

iwbnwif avatar May 27 '17 19:05 iwbnwif

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 avatar May 28 '17 16:05 psychegr

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.

psychegr avatar May 30 '17 17:05 psychegr

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.

iwbnwif avatar Jun 02 '17 13:06 iwbnwif