Microsoft & .NETVisual BasicVisualizing LINQ Sequences with GDI+

Visualizing LINQ Sequences with GDI+

Introduction

It’s all rectangles. Draw a rectangle and you need a rectangle control. Draw an ellipse and you need the bounding rectangle. Drawing controls, graphics, positioning forms—these all require rectangles.

My upcoming book, LINQ Unleashed for C# from Sams, talks about Language INtegrated Query. LINQ produces sequences. I wanted to visualize those sequences for the books—I got tired of creating them by hand in MS-Paint—so I thought I’d write a utility to generate the LINQ visualizations for me. Guess what? Rectangles are needed to lay out each item in the sequence.

In this article, a general utility for laying out rectangles and sub-rectangles in a line is provided. The utility also will lay out multiple rows of rectangles and sub-rectangles. Finally, the utility is used to display the results of LINQ queries. Enjoy.

Defining the Rectangle Utility

The basic objective is to define a simple utility that will lay out rectangles and sub-rectangles. The utility includes the ability to center a rectangle; this can be used to lay out text within the rectangle.

The solution chosen is a shared class that defines a couple of functions to return a rectangle and some number and position of smaller rectangles within the outer rectangle. The solution supports inserting padding between rectangles for a uniform, evenly spaced layout. Finally, some of the methods are overloaded to support rectangles with integer coordinates and those with floating point coordinates. Listing 1 contains the solution.

Listing 1: A general rectangle utility.

Imports System.Drawing

Public Class Rectangles

   ' example: a rectangle divided four across and three down
   ' get h=2 and v = 2 the x shows the sub-rect we would get
   ' -----------------
   ' |   |   |   |   |
   ' -----------------
   ' |   | x |   |   |
   ' -----------------
   ' |   |   |   |   |
   ' -----------------

   ' Given a rectangle return a sub-rectangle based on horizontal
   ' and vertical sub-divsions
   Public Shared Function GetSubRect(ByVal x As Integer, _
      ByVal y As Integer, _
      ByVal width As Integer, ByVal height As Integer, _
      ByVal horizontalSegment As Integer, _
      ByVal verticalSegment As Integer, _
      ByVal numberOfHorizontalSegments As Integer, _
      ByVal numberOfVerticalSegments As Integer) As Rectangle

      Return GetSubRect(x, y, width, height, horizontalSegment, _
         verticalSegment, numberOfHorizontalSegments, _
         numberOfVerticalSegments, 0)
      End Function

   ' Given a rectangle return a sub-rectangle based on horizontal
   ' and vertical sub-divsions
   Public Shared Function GetSubRectF(ByVal x As Integer, _
      ByVal y As Integer, _
      ByVal width As Single, ByVal height As Single, _
      ByVal horizontalSegment As Single, _
      ByVal verticalSegment As Single, _
      ByVal numberOfHorizontalSegments As Integer, _
      ByVal numberOfVerticalSegments As Integer) As RectangleF

      Return GetSubRect(x, y, width, height, horizontalSegment, _
         verticalSegment, numberOfHorizontalSegments, _
         numberOfVerticalSegments, 0)

   End Function

   ' Given a rectangle with padding in between
   ' return a sub-rectangle based on horizontal and vertical
   ' sub-divsions
   Public Shared Function GetSubRect(ByVal x As Integer, _
      ByVal y As Integer, _
      ByVal width As Integer, ByVal height As Integer, _
      ByVal horizontalSegment As Integer, _
      ByVal verticalSegment As Integer, _
      ByVal numberOfHorizontalSegments As Integer, _
      ByVal numberOfVerticalSegments As Integer, _
      ByVal padding As Integer) As Rectangle

      Dim newWidth As Integer = width / _
         numberOfHorizontalSegments - padding
      Dim newHeight As Integer = height / _
         numberOfVerticalSegments - padding

      Return New Rectangle(x + (newWidth + padding) * _
         horizontalSegment, _ y + (newHeight + padding) * 
         verticalSegment, _
         newWidth, newHeight)

   End Function

   ' Given a rectangle with padding in between
   ' return a sub-rectangle based on horizontal and vertical
   ' sub-divsions
   Public Shared Function GetSubRectF(ByVal x As Integer, 
      ByVal y As Integer, _
      ByVal width As Single, ByVal height As Single, _
      ByVal horizontalSegment As Single, _
      ByVal verticalSegment As Single, _
      ByVal numberOfHorizontalSegments As Integer, _
      ByVal numberOfVerticalSegments As Integer, _
      ByVal padding As Single) As RectangleF

      Dim newWidth As Single = width / _
         numberOfHorizontalSegments - padding
      Dim newHeight As Single = height / _
         numberOfVerticalSegments - padding

      Return New Rectangle(x + (newWidth + padding) * _
         horizontalSegment, y + (newHeight + padding) * _
         verticalSegment, _
         newWidth, newHeight)

   End Function

   ' Given a rectangle return a sub-rectangle based on horizontal _
   ' and vertical sub-divsions
   Public Shared Function GetSubRect(ByVal boundingRect _
      As Rectangle, _
      ByVal horizontalSegment As Integer, _
      ByVal verticalSegment As Integer, _
      ByVal numberOfHorizontalSegments As Integer, _
      ByVal numberOfVerticalSegments As Integer) As Rectangle

      Return GetSubRect(boundingRect.X, boundingRect.Y, _
         boundingRect.Width, _
         boundingRect.Height, horizontalSegment, _
         verticalSegment, numberOfHorizontalSegments, _
         numberOfVerticalSegments)

   End Function

   ' Given a rectangle return a sub-rectangle based on horizontal
   ' and vertical sub-divsions
   Public Shared Function GetSubRectF(ByVal boundingRect _
      As RectangleF, _
      ByVal horizontalSegment As Single, _
      ByVal verticalSegment As Single, _
      ByVal numberOfHorizontalSegments As Integer, _
      ByVal numberOfVerticalSegments As Integer) As RectangleF

      Return GetSubRectF(boundingRect.X, boundingRect.Y, _
         boundingRect.Width, _
         boundingRect.Height, horizontalSegment, _
         verticalSegment, numberOfHorizontalSegments, _
         numberOfVerticalSegments)

   End Function

   ' Given a rectangle with padding in between
   ' return a sub-rectangle based on horizontal and vertical
   ' sub-divsions
   Public Shared Function GetSubRect(ByVal boundingRect _
      As Rectangle, _
      ByVal horizontalSegment As Integer, _
      ByVal verticalSegment As Integer, _
      ByVal numberOfHorizontalSegments As Integer, _
      ByVal numberOfVerticalSegments As Integer, _
      ByVal padding As Integer) As Rectangle

      Return GetSubRect(boundingRect.X, boundingRect.Y, _
         boundingRect.Width, _
         boundingRect.Height, horizontalSegment, _
         verticalSegment, numberOfHorizontalSegments, _
         numberOfVerticalSegments, padding)

   End Function


   ' Given a rectangle with padding in between
   ' return a sub-rectangle based on horizontal and vertical
   ' sub-divsions
   Public Shared Function GetSubRectF(ByVal boundingRect _
      As RectangleF, _
      ByVal horizontalSegment As Single, _
      ByVal verticalSegment As Single, _
      ByVal numberOfHorizontalSegments As Integer, _
      ByVal numberOfVerticalSegments As Integer, _
      ByVal padding As Single) As RectangleF

      Return GetSubRectF(boundingRect.X, boundingRect.Y, _
         boundingRect.Width, _
         boundingRect.Height, horizontalSegment, _
         verticalSegment, numberOfHorizontalSegments, _
         numberOfVerticalSegments, padding)

   End Function


   ' Given the size of something get a centered horizontally
   ' and vertically centered rectangle
   Public Shared Function CenterRect(ByVal size As SizeF, _
      ByVal boundingRectangle As Rectangle)

      Return New Rectangle(boundingRectangle.X + _
         (boundingRectangle.Width - size.Width) / 2, _
          boundingRectangle.Y + (boundingRectangle.Height - _
             size.Height) / 2, _
          boundingRectangle.Width, _
          boundingRectangle.Height)

   End Function

End Class

The utility is capable of producing rectangle sub-divisions as suggested by the roughly created grid in the comment in the first part of the listing. You can specify the number of vertical and horizontal sub-rectangles, which horizontal and vertical sub-rectangle to return, and CenterRect (at the end) will return a rectangle centered vertically and horizontally within another rectangle.

The reason shared methods were used is that the Rectangles class itself stores no data; that is, it contains no fields, properties, or events. Following a general OOP rule such a class is naturally specified as a class containing all shared members.

Defining a State-Full Rectangle Manager

Next, I wanted to able to track individual rectangles, so a state-full RectangleManager was defined. This class keeps track of its horizontal and vertical sub-rectangles (with hSegments and vSegments), the desired padding between rectangles, and the original outer rectangle. This class is useful for keeping track of a specific rectangle set being used. Listing 2 contains the RectangleManager.

Listing 2: The RectangleManager is state-full—keeping track of the rectangle under management.

Imports System.Collections

Public Class RectangleManager
   'Implements IEnumerator
   ' note to self - implement IEnumerator so we can do a foreach
   ' over the segments


   Public Sub New(ByVal Rect As Rectangle, _
      ByVal hSegments As Integer, ByVal vSegments As Integer)
      Me.Rect = Rect
      Me.vSegments = vSegments
      Me.hSegments = hSegments
   End Sub

   Public Sub New(ByVal Rect As Rectangle, _
      ByVal hSegments As Integer, _
      ByVal vSegments As Integer, ByVal padding As Integer)
      Me.Rect = Rect
      Me.vSegments = vSegments
      Me.hSegments = hSegments
      Me.padding = padding
   End Sub

   Private Rect As Rectangle
   Private vSegments As Integer
   Private hSegments As Integer
   Private padding As Integer = 4

   Public Function GetSubRect(ByVal xSegment As Integer, _
      ByVal ySegment As Integer) As Rectangle
      Return Rectangles.GetSubRect(Rect, xSegment, ySegment, _
      hSegments, _
      vSegments, padding)
   End Function

   Public Function CenterRect(ByVal size As SizeF, _
      ByVal xSegment As Integer, _
      ByVal ySegment As Integer)
      Return Rectangles.CenterRect(size, GetSubRect(xSegment, _
         ySegment))
   End Function

End Class

Visualizing a LINQ Query with the Rectangle Manager

A LINQ query returns a sequence. (Think collection!) By using ellipses, you can visualize the sequence as a linear row of circles with each circle containing the text (or a representation of the text) in the circle itself. Listing 3 shows the form for representing the sequence visually, and Listing 4 contains a concrete behavior class that contains the draw-sequence behavior. (You can look up the State Behavior Pattern on dofactory.com or the GoF book on Design Patterns.)

Listing 3: The Form using a state behavior pattern; the Form shows the visual representation of the LINQ query.

Public Class Form1

   Private state As MyStatePattern

   Private Sub Form1_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load

      state = New DrawSequenceState(p)

   End Sub

   Private p As Pen = New Pen(Color.Red, 4)

   Private Sub Form1_Paint(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.PaintEventArgs) _
      Handles Me.Paint

      state.Draw(e.Graphics, Font)

   End Sub


   Private Sub DrawRectanglesToolStripMenuItem_Click(ByVal _
      sender As System.Object, _
      ByVal e As System.EventArgs) Handles _
      DrawRectanglesToolStripMenuItem.Click

      state = New DrawRectState(p)
      Invalidate()

   End Sub

   Private Sub DrawEllipsesToolStripMenuItem_Click(ByVal sender _
      As System.Object, _
      ByVal e As System.EventArgs) Handles _
      DrawEllipsesToolStripMenuItem.Click

      state = New DrawEllipseState(p)
      Invalidate()

   End Sub

   Private Sub DrawSequenceToolStripMenuItem_Click(ByVal sender _
      As System.Object, _
      ByVal e As System.EventArgs) Handles _
      DrawSequenceToolStripMenuItem.Click

      state = New DrawSequenceState(p)
      Invalidate()


   End Sub
End Class

Listing 4: The DrawSequenceState class that contains the sequence and the call using the RectangleManager to represent that sequence.

Imports System.Drawing

Public Class DrawSequenceState
   Inherits MyStatePattern

   ''' <summary>
   ''' Initializes a new instance of the DrawSequenceState class.
   ''' </summary>
   Public Sub New(ByVal pen As Pen)
      MyBase.New(pen)
   End Sub

   Public Overrides Sub Draw(ByVal Graphics As _
      System.Drawing.Graphics, _
      ByVal Font As System.Drawing.Font)
      Dim i = New Integer() {1, 2, 3, 4, 5, 6, 7, 8, 9}
      Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias

      Dim r As Rectangle = New Rectangle(10, 50, 200, 50)
      Graphics.DrawRectangle(MyPen, r)
      Dim result = From o In i Where o Mod 2 = 0 Select o

      Dim instance As RectangleManager = _
         New RectangleManager(r, result.Count, 1)

      For x = 0 To result.Count - 1

         Dim f As Rectangle = instance.GetSubRect(x, 0)
         Graphics.FillEllipse(Brushes.Lavender, f.X, f.Y, _
            f.Width, f.Height)
         Graphics.DrawEllipse(Pens.Black, f.X, f.Y, _
            f.Width, f.Height)
         Dim t As Rectangle = instance.GetSubRect(x, 0)

         Dim s As SizeF = Graphics.MeasureString( _
            result.ElementAt(x).ToString(), Font)
         Dim textRect As Rectangle = Rectangles.CenterRect(s, t)
         Graphics.DrawString(result.ElementAt(x).ToString(), _
            Font, _
            Brushes.Blue, textRect.X, textRect.Y)
      Next

   End Sub
End Class

The LINQ query is shown in Listing 4 on the line Dim result = From o In i Where o Mod 2 = 0 Select 0. This query selects the even numbered integers as shown in Figure 1.

Figure 1: The results of the LINQ query returning even numbers in a set of integers 0 through 8.

The base class for the state pattern MyStatePattern is shown in Listing 5.

Listing 5: The abstract base class for the state pattern.

Public MustInherit Class MyStatePattern

   Private FMyPen As Pen
   Public Property MyPen() As Pen
      Get
         Return FMyPen
      End Get
      Set(ByVal Value As Pen)
         FMyPen = Value
      End Set
   End Property

   Public Sub New(ByVal pen As Pen)
      FMyPen = pen
   End Sub

   Public MustOverride Sub Draw(ByVal Graphics As Graphics, _
      ByVal Font As Font)

End Class

Summary

Everything is Rectangles in a 2D rectilinear world. In this article, I demonstrated how you can draw specific rectangular sub-regions as rectangles or ellipses (or really as anything that is constrained by a rectangular region). That was followed by an application of this technique that visualizes LINQ queries. I hope you find the code useful.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his upcoming book LINQ Unleashed for C# due in Spring 2008. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org. Glugnet opened a users group branch in Flint, Michigan in August 2007. If you are interested in attending, check out the www.glugnet.org web site for updates.

Copyright © 2008 by Paul T. Kimmel. All Rights Reserved.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories