ZedGraph - How to make a horizontal line drag-able?

主宰稳场 提交于 2019-12-23 18:50:15

问题


I have some straight horizontal lines that I want the user be able to drag vertically. How would this be possible? I think the best parameter for line selection would be a fixed number of pixels near the line. So if mouse is +/- 2 pixels, I should change the mouse cursor and make the line drag-able.. I see the CurveItem class has properties IsSelectable and IsSelected. Will these have any function in solving this issue? I can’t really understand what they are for from reading the class documentation..


EDIT:

It seems that the FindNearestPoint (and FindNearestObject) only search actual points. How would I make selection to work along the whole section of a straight line? I guess I would need to make make my own custom "Find" routine that loops through all the lines I want to check, and for each calculate it's imaginary Y-point based on the mouse X position (?) I'm also thinking about sloped lines for this purpose, for horizontal/vertical lines it will be slightly simpler. At least it seems this is needed for a curveItem, but I assume the same must be done for selecting (at mid-section of) a LineObj?

I actually didn't know about the LineObj existed. It seems the LineObj is not possible to change the X2/Y2 coordinates, as they are ReadOnly. So is it at all possible to drag the X2/Y2 point of a LineObj?


EDIT 2:

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.

This is the code on mouseDown:

   ' Find nearest curve point:
   Dim ciNearestCurve As CurveItem
   Dim iNearestCurve As Integer
   Dim b As Boolean = zgc.GraphPane.FindNearestPoint(mousePt, zgc.GraphPane.CurveList, ciNearestCurve, iNearestCurve)
   If b Then
       With ciNearestCurve(iNearestCurve)
           Debug.Print(Date.FromOADate(.X) & " " & .Y & " " & .Z)
       End With

回答1:


Take a look on this tutorial on dragging the points with mouse.

If you are using a LineObj instead of a curve, take a look on the FindNearestObject method.

Also if you want to make some "area of sensitivity" for clicking, this method should help you to transform mouse coordinates in pixels to pane scale coordinates.

The main idea is to:
- subscribe for MouseDown, MouseUp and MouseMove events
- in the handler for MouseDown event check if clicked point is near the curve/graph object you want to move
- do the change in similar way that it is shown in example from the first link

EDIT
Regarding your edit:
Let's assume you have a horizontal curve myCurve containing two points. Using FindNearestPoint you can find nearest clicked point and the curve containing this point.

So you have:

// mousePt is where you clicked
CurveItem nearestCurve = null;
int nearestID = -1;

myPane.FindNearestPoint(mousePt, out nearestCurve, out nearestID);
if(nearestCurve!=null)
   // remember the curve somewhere. 

Next handle the MouseMove and MouseUp events to find out how much you need to move your curve. You need to know only the change in Y (or Y2) direction as the curve is horizontal and you probably do not want to move it along X axis.

When you'll find out how much you need to move your curve (dy), just do:

for(int i=0; i<nearestCurve.Points.Count; i++)
    nearestCurve.Points[i].Y += dy;

Regarding your second question, in the documentation for LineObj.Location.Y2 you have:

Note that the Y2 position is stored internally as a Height offset from Y.

And Width/Height properties can be set easily, so you can do it this way.




回答2:


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.



来源:https://stackoverflow.com/questions/3830209/zedgraph-how-to-make-a-horizontal-line-drag-able

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!