5

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.

Imran Ali Khan
  • 8,469
  • 16
  • 52
  • 77
hansioux
  • 229
  • 2
  • 9
  • Consider using ZedGraph, it is not bad. – David Mar 22 '13 at 08:45
  • Have you tried stepping through the code with the debugger to see what is going wrong? – Pieter Geerkens Mar 22 '13 at 08:49
  • Looks about right to me - I have similar code (except having gotten the X value I look it up in the series to get the nearest actual value). Are you getting any exeptions? What is `max`? – Matthew Watson Mar 22 '13 at 08:55
  • I think this is more of a case of I don't know which part of the chart structure can provide me with the Yvalue lookup ability. The only part wrong is I don't know what to replace chart1.ChartAreas[0].AxisY.PixelPositionToValue(e.Y); with. I have seen something like foreach (var series in chart1.Series) { all_Data_Values = all_Data_Values + Environment.NewLine + series.Name + ": " + series.Points[somepoint].YValues[0]; } But I couldn't get it to work either, I don't know what is used to index Points. – hansioux Mar 22 '13 at 08:57
  • @MatthewWatson hi, how did you lookup the nearest actual value, I think that's what I mean to ask. – hansioux Mar 22 '13 at 08:58

1 Answers1

5

(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;
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • Waston Thanks for your quick reply. I am trying to implement your method, however, I cannot use BinarySearch on my chart.Series[inex].Point. It is telling me that BinarySearch is not defined in 'System.Windows.Forms.DataVisualization.Charting.DataPointCollection" Not sure if I am missing a using or something. I can use BinarySearch on my Lists. – hansioux Mar 22 '13 at 09:42
  • Oh sorry, I forgot I had an extension method for binary searching an IList. Just a sec, I'll add it. – Matthew Watson Mar 22 '13 at 09:45