3

I'm going to draw a chart using c# in Windows Form Application. I need to draw a circle on line chart and show this data point value on a label which is nearest data point from the x-axis of the mouse pointer when mouse is moved over the chart area.

I write a code as follows......

private void Chart1_MouseMove(object sender, MouseEventArgs e)
    {
        HitTestResult result = Chart1.HitTest(e.X, e.Y);

        DataPoint nearestPoint = null;

        if (prevPosition!=null)
        {

            Chart1.Series[0].Points[prevPosition.PointIndex].MarkerStyle = MarkerStyle.None;


        }


        if (result.ChartElementType == ChartElementType.DataPoint)
        {


            string xValue = DateTime.FromOADate(Chart1.Series[0].Points[result.PointIndex].XValue).ToString("yyyy/MM/dd");
            string yValue = Convert.ToString(Chart1.Series[0].Points[result.PointIndex].YValues[0]);

            Chart1.Series[0].Points[result.PointIndex].MarkerStyle = MarkerStyle.Circle;
            Chart1.Series[0].Points[result.PointIndex].MarkerSize = 7;
            Chart1.Series[0].Points[result.PointIndex].MarkerColor = Color.Green;

            label1.Text = "Date:" + xValue;
            label2.Text = "Price:" + yValue;
            prevPosition = result;

        }

But this code shows the value and corresponding circle over line when mouse is moved near to the depicted line. When mouse is far away from line but within chart area it does not show the circle and value. I need to draw the circle over the line point nearest to the X-axis of the mouse pointer and show this data on a label

1 Answers1

3

You can find the closest point measuring only the x-values or the y-values or measuring the absolute distances. Or you could simple output the values under the mouse cursor, no matter the points. For this last one see here!

For each of the 1st three options this should help:

Class level variables used to set and reset colors..:

DataPoint dpXaxis = null;
DataPoint dpYaxis = null;
DataPoint dpAbs = null;

And a list of points for keeping the pixel locations of all points:

List<Point> pixPoints = null;

The MouseMove event:

private void chart_MouseMove(object sender, MouseEventArgs e)
{
    ChartArea ca = chart.ChartAreas[0];
    Axis ax = ca.AxisX;
    Axis ay = ca.AxisY;
    Series s = chart.Series[0];

    if (!s.Points.Any()) return; // no data, no action!

    // option 1:
    // the values at the mouse pointer:
    double valx = ax.PixelPositionToValue(e.X);
    double valy = ay.PixelPositionToValue(e.Y);

    // the deltas on the x-axis (with index):
    var ix = s.Points.Select((x, i) => new {delta = Math.Abs(x.XValue - valx), i})
                            .OrderBy(x => x.delta).First().i;
    var dpx = s.Points[ix];

    // option 2:
    // the deltas on the y-axis (with index):
    var iy = s.Points.Select((x, i) => 
                      new {delta = Math.Abs(x.YValues[0] - valy), i })
                     .OrderBy(x => x.delta).First().i;
    var dpy = s.Points[iy];

    // option 3:
    // the absolute distances (with index):
    var ind = pixPoints.Select((x, i) =>
        new { delta = Math.Abs(x.X - e.X) + Math.Abs(x.Y - e.Y), i}).
        OrderBy(x => x.delta).First().i;
    // find index of smallest delta
    var dpca = s.Points[ind];


    // set/reset colors
    if (dpXaxis != null) dpXaxis.Color = s.Color;
    dpXaxis = dpx;
    dpXaxis.Color = Color.LawnGreen;

    // set/reset colors
    if (dpYaxis != null) dpYaxis.Color = s.Color;
    dpYaxis = dpy;
    dpYaxis.Color = Color.Cyan;

    if (dpAbs != null) dpAbs.Color = s.Color;
    dpAbs = dpca;
    dpAbs.Color = Color.Red;
}

To find the point closest in both directions you will need to either include the scales of the axes or, probably easier, create a List<PointF> from the DataPoints that hold the locations of the points in pixels. For this use the reverse axis functions. Then I calculate the deltas in a similar fashion as the Linq above.

The list gets filled/updated like so:

List<Point> getPixPoints(Series s, ChartArea ca)
{
    List<Point> points = new List<Point>();
    foreach (DataPoint dp in s.Points)
    {
        points.Add(new Point(
            (int)ca.AxisX.ValueToPixelPosition(dp.XValue),
            (int)ca.AxisY.ValueToPixelPosition(dp.YValues[0])  ));
    }
    return points;
}

Let's see it at work:

enter image description here

TaW
  • 53,122
  • 8
  • 69
  • 111
  • Thanks. The first one is what I exactly wanted to do. – A. F. M. Golam Kibria Oct 06 '19 at 04:21
  • But sometimes it gives an error message. "Position argument should be in range from 0 to 100. Parameter name: position" at this line. double valx = ax.PixelPositionToValue(e.X); – A. F. M. Golam Kibria Oct 06 '19 at 04:42
  • That would be surprising. Could it be you wrote `PositionToValue` instead?? Positions are the 3rd coordinate system and refers to percentages.. – TaW Oct 06 '19 at 07:07
  • 1
    If I write PositionToValue it gives me same error. The problem actually occurs when there is no graph on the chart. In my case, the graph is drawn on a button click event. – A. F. M. Golam Kibria Oct 06 '19 at 09:43
  • Ah, ok. PositionToValue would be wrong. But if no data are on the chart the axes can't function. So you need to add a check to the start of the event. I have updated the answer.. – TaW Oct 06 '19 at 09:50