Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
327 views
in Technique[技术] by (71.8m points)

c# - hittest in polar chart with multiple series

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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);
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...