RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Use Transformations to Draw Your Own Great Graphs : Page 7

Use .NET to build your own graphing control that displays bar, line, and point data either on its own surface, in a printout, or in an image file.

Moving Data
It's only a small step from determining when the mouse is over a point to allowing the user to move that point. The GreatGraph control's MouseDown event handler shown in the following code starts the process. It calls DeviceToWorld to convert the mouse's current location into world coordinates. The code then calls the StartMovingPoint method.

StartMovingPoint loops through the DataSeries objects calling each one's StartMovingPoint method, which returns True if the DataSeries has a moveable point at the mouse's location. If the method returns True, the control's code removes the control's MouseMove event handler so the control doesn't try to update tooltips while the user is moving the point, and prevents the tooltip from showing data point values while the selected data point is moving:

   ' If the mouse is over a moveable data point.
   ' start moving it.
   Private Sub GreatGraph_MouseDown(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.MouseEventArgs) _
      Handles Me.MouseDown
      ' Convert the point into world coordinates.
      Dim x, y As Single
      DeviceToWorld(Me.PointToClient(Control.MousePosition), x, y)
      ' Start moving the data point if we can.
      StartMovingPoint(x, y)
   End Sub
   ' Start moving a data point if appropriate.
   Private Sub StartMovingPoint(ByVal x As Single, ByVal y As Single)
      ' See if the cursor is over a moveable point.
      For Each obj As DataSeries In m_GraphObjects
         If obj.StartMovingPoint(x, y) Then
            ' Uninstall our MouseMove event handler.
            RemoveHandler Me.MouseMove, _
               AddressOf GreatGraph_MouseMove
            Exit Sub
         End If
      Next obj
   End Sub
The rest of the work is done in the DataSeries class routines shown in the following code. The StartMovingPoint method returns False if the user is not allowed to move the DataSeries' data. It calls the FindPointAt method to see if there is a point at this position (download the code to see the details). If there is no point at this location, StartMovingPoint returns False. However, if there is a point at this location, the method installs event handlers provided by the DataSeries class to watch for MouseMove and MouseUp events generated by the parent GreatGraph control.

The DataSeries class's MouseMove event handler executes when the user has grabbed a data point in this DataSeries object and has moved the mouse. The event handler converts the mouse's position from device to world coordinates. It updates the data point's X and Y coordinates (whichever the user is allowed to change) and displays a tooltip showing the new value. It finishes by calling the GreatGraph control's Refresh method to make the control redraw the graph to show the new data.

When the DataSeries object's MouseUp event fires, the user has released the data point. The event handler removes the DataSeries object's MouseMove and MouseUp event handlers and calls the parent control's DataPointMoved method.

The GreatGraph control's DataPointMoved method simply restores the control's MouseDown event handler that was uninstalled by its StartMovingPoint subroutine:

   ' If (x, y) is over a moveable data point,
   ' install MouseMove and MouseUp event handlers
   ' to let the user move the point and return True.
   Private m_MovingPointNum As Integer = -1
   Friend Function StartMovingPoint( _
      ByVal x As Single, ByVal y As Single) As Boolean
      If (Not AllowUserChangeX) AndAlso (Not AllowUserChangeY) _
         Then Return False
      ' See if there's a data point here.
      m_MovingPointNum = FindPointAt(x, y)
      If m_MovingPointNum < 0 Then Return False
      ' Install our event handlers.
      AddHandler Parent.MouseMove, AddressOf Parent_MouseMove
      AddHandler Parent.MouseUp, AddressOf Parent_MouseUp
      Return True
   End Function
   ' The user is moving a data point.
   Private Sub Parent_MouseMove(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.MouseEventArgs)
      ' Get the mouse position in world coordinates.
      Dim x, y As Single
      Parent.DeviceToWorld(New PointF(e.X, e.Y), x, y)
      ' Move the point.
      If AllowUserChangeX Then Points(m_MovingPointNum).X = x
      If AllowUserChangeY Then Points(m_MovingPointNum).Y = y
      ' Set a new tooltip if appropriate.
      ShowDataTip(x, y)
      ' Redraw the graph.
   End Sub
   ' Stop moving the data point.
   ' Uninstall our MouseMove and MouseUp event handlers
   ' and let the parent know we moved the point.
   Private Sub Parent_MouseUp(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.MouseEventArgs)
      RemoveHandler Parent.MouseMove, AddressOf Parent_MouseMove
      RemoveHandler Parent.MouseUp, AddressOf Parent_MouseUp
   End Sub
Simple, Yet Powerful
The GreatGraph control is pretty powerful. By using a simple DataSeries object to represent data, the control lets you build curves that are linear, simple functions, and even curves that loop and cross themselves.

Figure 4. New Features: Adding new features to the DataSeries class such as filled areas and radial lines is relatively easy.
The DataSeries class draws line, bar, and point graphs, optionally with tick marks, labels, and maximum, minimum, and average lines. Keeping each of these features separate lets you build and debug them separately, making the class simple yet flexible.

For example, it took only a few minutes for me to add two new features to the DataSeries class. The first fills the area between the data and the X-axis as shown in the upper left graph in Figure 4. The second new feature is the ability to draw radial lines connecting each point to the origin and is illustrated in the upper right graph in Figure 4. (The control on the bottom demonstrates translucent bar graphs, an effect that you could already achieve by using translucent pens.)

Behind all of these drawing features, transformations play a critical role. They allow the DataSeries class to easily draw graphs on a GreatGraph control, a print preview, or a Bitmap. Transformations allow the code to correctly position text for labels and to map the mouse's position from device to world coordinates so the control can display tooltips and allow the user to move data points.

To learn more about transformations, see my previous article Terrific Transformations and my book Visual Basic 2005 Programmer's Reference.

Rod Stephens is a consultant and author who has written more than a dozen books and two hundred magazine articles, mostly about Visual Basic. During his career he has worked on an eclectic assortment of applications for repair dispatch, fuel tax tracking, professional football training, wastewater treatment, geographic mapping, and ticket sales. His VB Helper web site receives more than 7 million hits per month and provides three newsletters and thousands of tips, tricks, and examples for Visual Basic programmers.
Email AuthorEmail Author
Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date