Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get Y value across .NET chart series from X value

using C# with .NET chart.

I am trying to graph several waveforms, and I wish to move my mouse across the chart area, and have my tooltip display the Y value of each series in the chart at this X value location.

|      at xValue 12    |                                     |
|      _ = 3           |                                     |
|      * = 2           |                                * *  |
|              ________|______________________________*_____ |
|             /        |                             *       |
| __________*/*********|*****************************        |
|        *             |                                     |
|       *              |                                     |
|______________________|_____________________________________|

Kind of like this diagram above. Below is a version of my code:

void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            var pos = e.Location;
            _point.X = e.Location.X;
            _point.Y = e.Location.Y;

            try
            {
                if ((chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X) >= 0) && (chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X) <= max))
                {
                    //Crossair
                    chart1.ChartAreas[0].CursorX.SetCursorPixelPosition(_point, true);

                    //Tooltips
                    double xValue = chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X);
                    double yValue = chart1.ChartAreas[0].AxisY.PixelPositionToValue(e.Y);
                    string all_Data_Values = "";

                    foreach (var series in chart1.Series)
                    {
                        all_Data_Values = all_Data_Values + Environment.NewLine + series.Name + ": " + yValue;
                    }

                    tooltip.Show("At " + Math.Truncate(xValue * 1000) / 1000 + all_Data_Values, this.chart1, pos.X - 40, pos.Y - 20);
                }
            }
            catch (Exception exception)
            {
               //
            }
        }

This is what I have, and right now it only displays the Y value of my mouse cursor location. I have tried other codes, trying to somehow map the x values into chart1.Series[] but it didn't work either.

like image 781
hansioux Avatar asked Mar 22 '13 08:03

hansioux


1 Answers

(This is in response to the request for code to look up the nearest value given a pixel coord.)

I'm doing it a bit differently from you, because I'm actually setting the chart's "cursor" as the user moves the mouse around, but hopefully this will give you enough information for you to adapt it to your needs...

Here's how I calculate the X axis coord for a client X coord:

private double calcCursorGraphX(int clientX)
{
    var xAxis = _chart.ChartAreas[CHART_INDEX].AxisX;
    int xRight = (int) xAxis.ValueToPixelPosition(xAxis.Maximum) - 1;
    int xLeft = (int) xAxis.ValueToPixelPosition(xAxis.Minimum);

    if (clientX > xRight)
    {
        return xAxis.Maximum;
    }
    else if (clientX < xLeft)
    {
        return xAxis.Minimum;
    }
    else
    {
        return xAxis.PixelPositionToValue(clientX);
    }
}

Given an X value returned from the above method, we can look up the nearest preceeding value:

private int nearestPreceedingValue(double x)
{
    var bpData  = _chart.Series[SERIES_INDEX].Points;
    int bpIndex = bpData.BinarySearch(x, (xVal, point) => Math.Sign(x - point.XValue));

    if (bpIndex < 0)
    {
        bpIndex = ~bpIndex;                // BinarySearch() returns the index of the next element LARGER than the target.
        bpIndex = Math.Max(0, bpIndex-1);  // We want the value of the previous element, so we must decrement the returned index.
    }                                      // If this is before the start of the graph, use the first valid data point.

    return bpIndex;
}

Then you have an index which you can use to look up the value from _chart.Series[SERIES_INDEX].Points

I'm not sure if this fits with the way that your data is stored in the charts, but that's how I do it.

[EDIT] Here's the missing BinarySearch extension method. Add it to a static class somewhere accessible. Replace the "Contracts" stuff with your own error handling if you're not using Code Contracts.

/// <summary>
/// Searches the entire sorted IList{T} for an element using the specified comparer 
/// and returns the zero-based index of the element.
/// </summary>
/// <typeparam name="TItem">The type of the item.</typeparam>
/// <typeparam name="TSearch">The type of the searched item.</typeparam>
/// <param name="list">The list to be searched.</param>
/// <param name="value">The value to search for.</param>
/// <param name="comparer">The comparer that is used to compare the value with the list items.</param>
/// <returns>
/// The zero-based index of item in the sorted IList{T}, if item is found; 
/// otherwise, a negative number that is the bitwise complement of the index of the next element that is larger than item,
/// or - if there is no larger element - the bitwise complement of Count.
/// </returns>

public static int BinarySearch<TItem, TSearch>(this IList<TItem> list, TSearch value, Func<TSearch, TItem, int> comparer)
{
    Contract.Requires(list != null);
    Contract.Requires(comparer != null);

    int lower = 0;
    int upper = list.Count - 1;

    while (lower <= upper)
    {
        int middle = lower + (upper - lower) / 2;
        int comparisonResult = comparer(value, list[middle]);

        if (comparisonResult < 0)
        {
            upper = middle - 1;
        }
        else if (comparisonResult > 0)
        {
            lower = middle + 1;
        }
        else
        {
            return middle;
        }
    }

    return ~lower;
}
like image 110
Matthew Watson Avatar answered Oct 19 '22 05:10

Matthew Watson