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
