Firstly to answer to bretddog:
It seems to be an issue with the FindNearestPoint on a JapaneseCandleStick graph; When I click in the graph pane, it does not return the index of the nearest bar, but I believe it instead selects the index with the closest Y-value, no matter how far away on the x axis it is. Sometimes it's a bar to the right of the mouse, sometimes to the left of the mouse. Is this the way it's meant to work?
I made this custom function myself, so I guess it's ok.. Still it would be nice to understand why the FindNearestPoint acts this way
I don't work with JapaneseCandleStick but with Line, but I think it's the same kind of problem. ZedGraph works with coordinates, so with points, not with functions, so to determine the nearest "Curve" it should interpolate and it seems very hard to do that.
Nevertheless, for Line graphics I've develop a function to obtain the nearest curve. So I've made a straight line interpolation between each consecutive points for each curve, and I've used the mathematical distance to determine the nearest curve. The code is:
''' <summary>
''' To obtain the nearest curve and its index on ZedGraph stick
''' </summary>
''' <param name="GraphPane">The graphpane on wich you are working</param>
''' <param name="PointLocation">Mouse location</param>
''' <param name="NearestCurve">Reference of the nearest curve</param>
''' <param name="NearestCurveIndex">Index of the nearest curve</param>
''' <returns>True if a curve is found</returns>
''' <remarks></remarks>
Private Function FindNearestCurve(ByVal GraphPane As ZedGraph.GraphPane, ByVal PointLocation As System.Drawing.Point, ByRef NearestCurve As CurveItem, ByRef NearestCurveIndex As Integer) As Boolean
Try
Dim MinDist As Double = -1 'error if < 0
Dim DistTemp As Double
Dim a, b As Double
Dim Curve As CurveItem
Dim ValX, ValY As Double
Dim NormX, NormY As Double
'ini
NearestCurveIndex = -1
GraphPane.ReverseTransform(PointLocation, ValX, ValY) 'To use real values
NormX = GraphPane.XAxis.Scale.Max - GraphPane.XAxis.Scale.Min 'To normalize value when we haven't orthonormal axis
NormY = GraphPane.YAxis.Scale.Max - GraphPane.YAxis.Scale.Min 'To normalize value when we haven't orthonormal axis
'We looking for the nearest curve
For j = 0 To GraphPane.CurveList.Count - 1
Curve = GraphPane.CurveList.Item(j)
If Curve.IsVisible = True Then
'We generate all coefficient (a and b) of straight line interpolation (equation y=ax+b)
For i = 0 To Curve.NPts - 2 '-2 because we work on intervals
'we check if interval is close to the point (to prevent case where the complete interpolation curve is the nearest curve but the real segment is far to the point)
If (Curve.Points.Item(i + 1).Y >= ValY And Curve.Points.Item(i).Y <= ValY) Or
(Curve.Points.Item(i + 1).Y <= ValY And Curve.Points.Item(i).Y >= ValY) Or
(Curve.Points.Item(i + 1).X >= ValX And Curve.Points.Item(i).X <= ValX) Or
(Curve.Points.Item(i + 1).X <= ValX And Curve.Points.Item(i).X >= ValX) Then
'We calculate straight line interpolation coefficient a and b
'Vertical line case
If (Curve.Points.Item(i + 1).X / NormX - Curve.Points.Item(i).X / NormX) = 0 Then
'We calculate directly the distance
DistTemp = Math.Abs(Curve.Points.Item(i).X / NormX - ValX / NormX)
Else 'All other case
'a = (yi+1 - yi) / (xi+1 - xi)
a = (Curve.Points.Item(i + 1).Y / NormY - Curve.Points.Item(i).Y / NormY) / (Curve.Points.Item(i + 1).X / NormX - Curve.Points.Item(i).X / NormX)
'b = yi - a*xi
b = Curve.Points.Item(i).Y / NormY - a * Curve.Points.Item(i).X / NormX
'We calculate the minimum distance between the point and all straight line interpolation
DistTemp = Math.Abs(a * ValX / NormX - ValY / NormY + b) / Math.Sqrt(1 + a * a)
End If
'We test if it's the minimum and save corresponding curve
If MinDist = -1 Then
MinDist = DistTemp 'first time
NearestCurveIndex = j
ElseIf DistTemp < MinDist Then
MinDist = DistTemp
NearestCurveIndex = j
End If
End If
Next
End If
Next
'Return the result
If NearestCurveIndex >= 0 And NearestCurveIndex < GraphPane.CurveList.Count Then
NearestCurve = GraphPane.CurveList.Item(NearestCurveIndex)
Return True
Else
NearestCurve = Nothing
NearestCurveIndex = -1
Return False
End If
Catch ex As Exception
NearestCurve = Nothing
NearestCurveIndex = -1
Return False
End Try
End Function
I've tested this function, and it seems to work well, but I can't guarantee in all cases (indeed, if the first/last point of a curve is the nearest point, it won't be detected as such). Some remarks about using:
- Work only on visible curve, to remove it, remove If Curve.IsVisible = True Then line ;
- I've normalized X,Y values before the calculation to prevent disagreement with a non-orthonormal axis;
- When there is an error, return False and you have NearestCurve = Nothing and NearestCurveIndex = -1;
- The line cursor that you want to drag should be a curve (with to points or more, no matter), not a LineObj;
- The test if the interval is close or not is the weak part of the code, and on which I suppose it should happen some mistakes (I've already identified one - rare - case, as said before). A problem can also appear if you have a not perfect vertical line (so with a very big a coefficient).
Lastly, I'm not sure that this code is optimized for speed, but I've no freezing on my side. The best way should be to integrate the function to a ZedGraph class, and calculate each coefficient (a and b) when Add function is called, so to not calculate them each time (so each mouse move).
So I hope that code will help some people to create a movable cursor, something which is very missing in ZedGraph.