2

I have a polar chart with multiple series. I want to have a functionality to click on one of the datapoints in any series and perform something. I tried to use the HitTest and it works on a single series. The problem is when I used in on a chart with multiple series and sometimes when I click on a datapoint, it returns a different point. Please help.

This is the snippet that I used.

HitTestResult result = chart1.HitTest(e.X, e.Y, ChartElementType.DataPoint);
if (result.ChartElementType == ChartElementType.DataPoint)
{
    var xVal = result.Series.Points[result.PointIndex].XValue;
    var yVal = result.Series.Points[result.PointIndex].YValues;
    result.Series.Points[result.PointIndex].MarkerColor = Color.Black;
}

update:

Thanks you so much for bearing with me. Anyway, this is the code incorporating what you suggested.

DataPoint dpCurrent = null;
    int normalMarkerSize = 10;
    int largeMarkerSize = 15;

    private void chart1_MouseClick(object sender, MouseEventArgs e)
    {
        HitTestResult result = chart1.HitTest(e.X, e.Y, ChartElementType.DataPoint);
        if (result.ChartElementType == ChartElementType.DataPoint)
        {
            dpCurrent = result.Series.Points[result.PointIndex];
            if (distance(PolarValueToPixelPosition(dpCurrent, chart1, result.ChartArea), e.Location) <= 5)
                result.Series.Points[result.PointIndex].MarkerColor = Color.Black;
        }
    }

However, I noticed that the value of "phi" in the PolarValueToPixelPosition is always returning NaN

overmind
  • 467
  • 1
  • 8
  • 17
  • This works fine here. How exactly do you know the the wrong marker is colored? Maybe the points are overlapping? You do realize that the hittest repots the datapoint not only when clicking the __marker__ but also the __line__; in a __polar__ chart the point that is __closest__ is reported! – TaW Jul 19 '16 at 09:35
  • You may want to do a preview of the point in the mousemove by setting its color to some other color of by making it larger until the mouse if off it.. – TaW Jul 19 '16 at 09:42
  • Hi @TaW, thanks for the reply. Actually, my chart contains almost one thousand points. I just hide the lines so only the points are visible in the graph. Is there a way so that the lines will be excluded when I click the chart. I just want the points. – overmind Jul 19 '16 at 11:40
  • I'm afraid that using Hittest will not allow you to ignore the lines. One alternative would be to do your own hittest but this involves calculating the coordinates of the datapoints. This is simple for other chart types but not for polar charts. So atm I don't have a better simple idea than to provide feedback by making the currently targetted datapointmarker larger until the mouse is off it.. – TaW Jul 19 '16 at 12:14
  • See my updated answer, which should do the job of making sure that only the markers can be clicked.. – TaW Jul 19 '16 at 15:37

1 Answers1

2

Here are two ways to solve the problem; none can actually make the HitTest ignore clicking on the connecting lines.

But they should be fine, espacially when you implement them both.

The first provides feedback to the user so he can see in advance which point the mouse is over and he is about to click:

DataPoint dpCurrent = null;
int normalMarkerSize = 8;
int largeMarkerSize = 12;

private void chart1_MouseMove(object sender, MouseEventArgs e)
{
    HitTestResult hit =  chart1.HitTest(e.X, e.Y);
    if (hit.ChartElementType == ChartElementType.DataPoint)
    {
        dpCurrent = hit.Series.Points[hit.PointIndex];
        dpCurrent.MarkerSize = largeMarkerSize;
    }
    else
    {
        if (dpCurrent != null) dpCurrent.MarkerSize = normalMarkerSize;
        dpCurrent = null;
    }

Unfortunately the HitTest will still trigger a DataPoint-hit even if you only hit the connecting lines, no matter how thin you make them or what color they have..

..enter solution two:

One can write a custom HitTest by calculating the pixel-coordinates of the DataPoints; this is not as simple as calling the Axis.ValueToPixelPosition methods as it involves some modest amount of math..:

Now before processing the hit you would do an additional check, maybe like this:

   if (distance(PolarValueToPixelPosition(dpCurrent, chart1,
       hit.ChartArea), e.Location) <= markerRadius) ...//do the hit stuff

Here is the coordinate transformation function:

PointF PolarValueToPixelPosition(DataPoint dp, Chart chart, ChartArea ca)
{
    RectangleF ipp = InnerPlotPositionClientRectangle(chart, ca);
    double crossing = ca.AxisX.Crossing != double.NaN ? ca.AxisX.Crossing : 0;

    // for RangeChart change 90 zo 135 !
    float phi = (float)(360f / ca.AxisX.Maximum / 180f * Math.PI *   
             (dp.XValue - 90 + crossing ) );

    float yMax = (float)ca.AxisY.Maximum;
    float yMin = (float)ca.AxisY.Minimum;
    float radius = ipp.Width / 2f;
    float len = (float)(dp.YValues[0] - yMin) / (yMax - yMin);
    PointF C = new PointF(ipp.X + ipp.Width / 2f, ipp.Y + ipp.Height / 2f);

    float xx = (float)(Math.Cos(phi) * radius * len);
    float yy = (float)(Math.Sin(phi) * radius * len); 
    return new PointF(C.X + xx, C.Y + yy);
}

It makes use of a simple distance function..:

float distance(PointF pt1, PointF pt2)
{
    float d = (float)Math.Sqrt((pt1.X - pt2.X) * (pt1.X - pt2.X) 
                                + (pt1.Y - pt2.Y) * (pt1.Y - pt2.Y));
    return d;
}

Also of two other useful functions to calculate the pixel size of the InnerPlotPosition, which I have used in quite a few answers now..:

RectangleF ChartAreaClientRectangle(Chart chart, ChartArea CA)
{
    RectangleF CAR = CA.Position.ToRectangleF();
    float pw = chart.ClientSize.Width / 100f;
    float ph = chart.ClientSize.Height / 100f;
    return new RectangleF(pw * CAR.X, ph * CAR.Y, pw * CAR.Width, ph * CAR.Height);
}

RectangleF InnerPlotPositionClientRectangle(Chart chart, ChartArea CA)
{
    RectangleF IPP = CA.InnerPlotPosition.ToRectangleF();
    RectangleF CArp = ChartAreaClientRectangle(chart, CA);

    float pw = CArp.Width / 100f;
    float ph = CArp.Height / 100f;

    return new RectangleF(CArp.X + pw * IPP.X, CArp.Y + ph * IPP.Y,
                            pw * IPP.Width, ph * IPP.Height);
}
TaW
  • 53,122
  • 8
  • 69
  • 111
  • Hi, I really appreciate the help you have given me. But I am not sure I follow on how to use the functions you have written and what is the value of the markerRadius by the way? Thank you. – overmind Jul 20 '16 at 04:26
  • Well, your markers have a size. The custom hittest is supposed to test if the mouse is on a marker and nor just on a line. Half the markersize is the allowed distance from the datapoint. So you add the test (`if (distance..)`) to the test you already have (`if (result.ChartElementType..`)... Keep asking until we have cleared it all up! – TaW Jul 20 '16 at 06:34
  • hi, i appreciate your help. A sample code is updated above. However, I always seem to get a NaN value of phi. Do you know what this happens? thanks! – overmind Jul 20 '16 at 07:35
  • Hm, please use the debugger to find out what happens in the line that caluclates phi. I though the Maximum is always set (and >0) for a polar chart; and the crossing defaults to 0..? Please report back with the values you get for those and also dp.XValue.. What is your xvalue-type? – TaW Jul 20 '16 at 08:32
  • Did you make progress? – TaW Jul 21 '16 at 07:58
  • Hi, sorry for the late reply. It seems the ca.AxisX.Crossing is returning a NaN value. This is the reason why the phi value is also Nan. When I removed it, it seems to work. But I have not fully tested it all the points. Is it ok to remove this? Thank you so much for your help – overmind Jul 21 '16 at 09:15
  • Yes, by all means remove it if you don't set a Crossing anyway! I have adapted the code. – TaW Jul 21 '16 at 09:16