hittest in polar chart with multiple series

后端 未结 1 907
闹比i
闹比i 2020-12-07 05:54

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 a

相关标签:
1条回答
  • 2020-12-07 06:17

    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);
    }
    
    0 讨论(0)
提交回复
热议问题