Search code examples
c#mschart

Axis drawing over point and preventing selection in DataVisualization.Charting


I am using a circular Marker to indicate the position of data points. When the points are drawn on an axis I cannot select them (using HitTest)... but I can select them when they are drawn anywhere else on the chart. The unselectable points appear to be drawing under the axis. If I make the markers larger then I can select the points on the axis by clicking at the edge of the circle.

Is there a way to get the markers to draw on top of the axis (if that's the problem!)?

enter image description here


Solution

  • This is a really good question.

    The problem is indeed real I don't know a simple solution.

    • Here is one possible workaround:

    You can modify the X you test at when you find that you have hit the Axis or an AxisLabel or a TickMark. Calculate the position mirrored at the axis ond offset by its width and test again.

    For this you need to know the axis position in pixels and its width.

    The latter is axis.Linewidth. For the former use the axis function ax.ValueToPixelPosition. It takes the x-value at the y-axis position. This will either be x-axis' Minimum (if it is set) or 0 or some automatic value, depending on your data.

    This does improve the situation although I found that TickMarks still tend to get in the way, not sure why.

    Users would love if the point the mouse is really near, would grow until they go away again. You can use the reverse function PixelPositionToValue and some Linq to find the closest point.

    Update:

    • In fact the last idea can be implemented as an alternative workaround.

    Here is an example of a function to find the closest point, maybe in the MouseMove event or maybe after the HitTest has hit an axis or axis element..:

    DataPoint GetNearestPoint(Series s, ChartArea ca, Point pt)
    {
        int limit = s.MarkerSize * 4;  // pick a suitable distance!
    
        Axis ay = ca.AxisY;
        Axis ax = ca.AxisX;
    
        var mins = s.Points.Cast<DataPoint>().Select((x, index) =>
            new
            {
                val = Math.Abs(pt.Y - (int)ay.ValueToPixelPosition(x.YValues[0]))
                        +  Math.Abs(pt.X - (int)ax.ValueToPixelPosition(x.XValue)),
                ix = index
            });
    
        var min = mins.Where(x => x.val == mins.Min(v => v.val)).Select(x => x.ix).First();
        if (mins.ElementAt(min).val > limit) return null;
        else return s.Points[min];
    }
    

    Here is how I call it after creating a class level variable:

    DataPoint lastHit = null;
    

    It colors the closest point while the cursor is close enough..:

        var nearest = GetNearestPoint(someSeries, someChart.ChartAreas[0], e.Location);
        if (lastHit != null) lastHit.MarkerColor =  someSeries.Color;
        if (nearest != null)
        {
            lastHit = nearest;
            nearest.MarkerColor = Color.Red;
        } 
    

    Enlarging the MarkerSize is if course just as simple (at least unless your ChartType is Bubble, as is mine ;-)

    enter image description here

    • Yet another workaround would be rather messy: You could ad an extra ChartArea and overlay it exactly over the original one. You would have to get the ElementPositions of the original and also of its InnerPlotPosition and then use them for the overlay. Now you could maybe move the series to the 2nd, upper ChartArea and also turn its Axis etc off.. The HitTest should work as expected now. But the positions need to be adjusted upon each resize.