by Dr. Peter Wayne
Alpha Five doesn't provide a built-in way to draw pie graphs, but that doesn't mean it can't be done. Here is a form I created in Alpha Five:
Figure 1. No, this is not an optical illusion. This pie chart was created in Alpha Five v.4.
Before you go looking through the Alpha Five manual for graphs, let me tell you now that you won't find it there. The graphing functions that we'll use are part of Windows.
Back in the days of DOS, any time a programmer wanted to do something, he or she had to call on the hardware directly. Not only did this result in a duplication of effort, but in any kind of multitasking environment it is simply verboten to allow one program to hog the whole CPU. Windows provides a layer of insulation between the programmer and the hardware. This insulation consists of libraries of functions primarily intended to allow a C++ programmer to interact with the operating system. These functions are known as the Windows Application Programming Interface, or WinAPI for short. Most of the functions of the WinAPI reside in dynamic link libraries, or dlls, although some of them are in other files.
Microsoft documents the WinAPI in its Software Development Kit (SDK), something I don't own and have little intention of buying. You can learn a lot, however, from Paul Kuliniewicz's Visual Basic site on the WinAPI, www.vbapi.com. Mr. Kuliniewicz has a useful introduction to the API as well as an numerous examples of how a Visual Basic programmer can call the API functions.
Of course, the developers of Alpha Five are no dummies, and they have provided us with a way to hook into the WinAPI.
Xbasic's DECLARE statement tells Xbasic that you are referencing an external function, generally part of the WinAPI. Most external functions take 32-bit numeric arguments, known as "long" integers. Some of them will also take strings, and others will take other, specially-defined "structures" as arguments. Let me show you how to take a Visual Basic API call and translate it into Xbasic:
Mr. Kuliniewicz provides the Visual Basic syntax to get the user's computer name from the WinAPI as
| Declare Function GetComputerName Lib "kernel32.dll" Alias "GetComputerNameA" (ByVal lpBuffer As String, nSize As Long) As Long Platforms: Win 95/98, Win NT GetComputerName reads the name of the user's computer. The name is put into the string variable passed as lpBuffer. The function returns 0 if an error occured or 1 if successful. lpBuffer A string large enough to hold the returned computer name terminated by a null character. nSize The length in characters of lpBuffer. |
I won't go into all the details of the Visual Basic syntax, but let's examine the definition. The actual function name in the WinAPI is GetComputerNameA, but Visual Basic is able to refer to the function with an alias of GetComputerName. The function is located in the kernel32.dll dynamic linked library. The function returns a long 32-bit integer, and it takes 2 arguments: a string (what we think of as a character variable in Xbasic) and another long 32-bit integer.
There are a few wrinkles here, both for the Visual Basic programmer and the Xbasic programmer. Visual Basic passes function arguments by reference unless explicitly declared otherwise. When an argument is passed by reference, the actual value of the variable being used is not passed, but instead the address of that variable is passed. When a variable is passed by value, then the actual contents of the variable are passed to the function. In the declaration of GetComputerName, the string variable is passed by value and the long integer variable is passed by reference.
When we convert the function to its Xbasic equivalent, we have to translate the arguments to Xbasic arguments. Xbasic can only pass string variables to external functions by reference; it can pass numeric variables either by value or by reference. You must use dot-variable notation to pass values by reference in Xbasic. You can review this subject in my article on Dot Variables.
In Xbasic, you can create a "buffer" or block of memory in which to store a string by defining it with the declarestruct keyword. The syntax to create a 255-byte block of memory in which to store a character variable is
declarestruct text c255text
Loosely translated, this means "Create a new variable type, text , that is 255 characters long, and create a variable text whose type is that of the new structure.
The Xbasic code to obtain the computer name is
declarestruct text c255text
declarestruct length L1length
Declare kernel32 GetComputerNameA L(text)(length)
temp.text="" 'create a dot variable, or pointer, to text
len.length=255 ' do same with length
result=GetComputerNameA(temp,len) 'pass 2 pointers to GetComputerNameA
ui_msg_box("Your computer name",temp.text)
Script 1. GetComputerName WinAPI call.
Notice in Script 1 that both the arguments are passed as structures to the GetComputerNameA function. That allows each argument to be passed by reference. The first line creates the text structure. The second line creates another structured variable, length, which is merely a long integer. The third line declares the interface to the WinAPI call in the order of return_type followed by argument_types. The full meaning of the third line is "There is an external function, GetComputerNameA, in kernel32.dll. It returns a long integer and takes as arguments a 255-byte character buffer and another long integer. Both the arguments are passed by reference."
You should be able to run Script 1 in A5v4. On my computer, it gives me this message:

Figure 2. Result of running Script 1.
As long as we're delving into the WinAPI, let's write a handy Xbasic function to return the Windows user logon name. We can use the GetUserName function in the WinAPI. Here is the Visual Basic definition:
Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long
We can now write an Xbasic function to return the user name:
function GetUserName as C() declarestruct text c255text declarestruct length L1length declare advapi32 GetUserNameA L(text)(length) temp.text="" len.length=255 result=GetUserNameA(temp,len) GetUserName=temp.text end function
Script 2. The GetUserName() function.
Here is the function in the Interactive Window:
Figure 3. The GetUserName() function returns the Windows logon name.
I just knew that some of you would like this function!
Even if you've followed me so far - and don't be embarrassed if you have to read this article 2 or 3 times. None of this was obvious to me at first, and even now I don't understand the API conventions fully. Nevertheless I am able to bumble along, and if you don't mind an occasional fall down the stairs, you are welcome to explore with me.
All of this fooling around with the WinAPI has been prelude to producing the pie chart in Figure 1. Although Alpha Five doesn't provide tools for drawing pie slices, Windows does, with the appropriately name Pie function call. Here again is the Visual Basic syntax as taken from Mr. Kuliniewicz:
| Declare Function Pie Lib "gdi32.dll" (ByVal
hdc As Long, ByVal X1 As Long, ByVal Y1 As Long, ByVal X2 As Long, ByVal Y2 As
Long, ByVal X3 As Long, ByVal Y3 As Long, ByVal X4 As Long, ByVal Y4 As Long)
As Long Pie draws an elliptical pie wedge on a device. The pie wedge is drawn using the device's currently selected pen and is filled using its currently selected brush. The pie wedge consists of two radials from the ellipse's center to the ellipse's edge, filling the area between them (going counterclockwise around the ellipse). The first two sets of (x,y) coordinate pairs specify the bounding rectangle which determines the ellipse. The last two sets of (x,y) pairs determine the points along the ellipse; the start and endpoints are determined by the intersection of a ray from the ellipse's center through the (x,y) coordinate and the ellipse. The function returns 1 if successful, or 0 if an error occured. hdc A device context to the device to draw the chord on. X1 The x-coordinate of the upper-left corner of the ellipse's bounding rectangle. Y1 The y-coordinate of the upper-left corner of the ellipse's bounding rectangle. X2 The x-coordinate of the lower-right corner of the ellipse's bounding rectangle. Y2 The y-coordinate of the lower-right corner of the ellipse's bounding rectangle. X3 The x-coordinate of the point determining the starting point of the pie wedge. Y3 The y-coordinate of the point determining the starting point of the pie wedge. X4 The x-coordinate of the point determining the ending point of the pie wedge. Y4 The y-coordinate of the point determining the ending point of the pie wedge. |
The Visual Basic reference to the Pie function. Refer to www.vbapi.com for more information.
This function is considerably more complex than the WinAPI calls we introduced before, in fact, my first perusal produced an uneasy feeling midway between seasickness and migraine. Nevertheless I firmly reminded myself that anything Bill Gates can do, I can do too, except that I get paid less, and I was able to abstract the essentials: the Pie function draws a pie slice within a rectangle. Here's an example of a single call to Pie:
Figure 4. A circular slice within a bounding rectangle.
The Pie function draws the slice to something called a device context. A device context is simply whatever we are currently drawing on. In Alpha Five, the only thing the Xbasic programmer can directly doodle on is a User Object.
The first step in designing the Pie chart was to place a User Object on a blank form:

Figure 5. The User Object icon is the blue gear at the bottom of the Toolbox.
A User Object just looks like a blank space. It is a blank space - but you can place text and graphics on it with WinAPI calls!
I sized my user object to be exactly 2.6 inches square:

Figure 6. The User Object is 2.6 in. by 2.6 in.
Windows measures distances in twips, of which there are 120 to each inch, so my user object is a square, each side measuring 312 twips (120*2.6). The center of my user object is the point (156,156), the upper-left-hand corner is (0,0), and the bottom right is (312,312).
If I right-click on the User Object and pick Actions, I discover that there are a series of events associated with a User Object:

Figure 7. Drop-down list of User Object events.
There is only one event that interests me for this object: the OnDrawevent. I want to place my pie slice in the object when the user object is drawn, or displayed, on the form. By studying the code for the Clock example and holding my breath only until I turned light blue I was able to figure out that I needed few more WinAPI calls. I won't retrace all the steps but will instead show the finished pie slice:
declare gdi32 CreateSolidBrush LL declare gdi32 SelectObject LLL declare gdi32 DeleteObject LL Declare gdi32 Pie LLLLLLLLLL ' my box is 312 x 312 greenish=150*256 ' a bright green color hbrush = CreateSolidBrush(greenish) SelectObject(a_user.draw.hdc, hbrush) Pie(a_user.draw.hdc, 0, 0, 312, 312, 312, 156, 156, 0) DeleteObject(hbrush)
Script 3. Draw a green quarter-circle.
Let's examine Script 3 a little more closely. First, I define all the WinAPI calls I'll be using. There are 4 calls:
Here's the result of viewing the form that contains this User Object:

Figure 8. Here is a green quarter circle on a form.
If you view this form, you'll notice a peculiarity. If you switch focus from Alpha Five to another Windows application and back again, the background of the other application will bleed through the User Object on this form. That's because we're not explicitly telling Windows to repaint the background. To do that, we need to add a call to the WinAPI Rectangle function to paint the rest of the User Object:
declare gdi32 CreateSolidBrush LL declare gdi32 SelectObject LLL declare gdi32 DeleteObject LL Declare gdi32 Pie LLLLLLLLLL Declare gdi32 Rectangle LLLLLL ' my box is 312 x 312 grayish= 8421504 ' color in background rectangle hbrush=CreateSolidBrush(grayish) SelectObject(a_user.draw.hdc, hbrush) Rectangle(a_user.draw.hdc,0,0,312,312) DeleteObject(hbrush) ' now do pie slice greenish=150*256 ' a bright green color hbrush = CreateSolidBrush(greenish) SelectObject(a_user.draw.hdc, hbrush) Pie(a_user.draw.hdc, 0,0, 312, 312, 312, 156,156,0) DeleteObject(hbrush)
Script 4. This script will produce a slice with a gray background, as in Figure 4.
You can see that the call to Rectangle() is just like the call to Pie(), except that a rectangle is a simpler object to draw. Notice that Pie() has to follow Rectangle(), otherwise the gray rectangle would erase the pie slice.
By now you should be glimpsing something that took me longer than I care to admit to grasp: if you want to draw in Windows, then all you have to do is
It doesn't seem very hard when put that way!
Now it's time to return to the pie graph in Figure 1. Here is the approach I'm going to take:
Here is the OnInit code for the User Object:
declare gdi32 CreateSolidBrush LL declare gdi32 SelectObject LLL declare gdi32 DeleteObject LL dim shared inputs[6] as n dim shared names[6] as c inputs[1]=1.1 names[1]="Paradox" inputs[2]=2.2 names[2]="Filemaker Pro" inputs[3]=3.3 names[3]="SQL server" inputs[4]=4.4 names[4]="Access" inputs[5]=5.5 names[5]="Oracle" inputs[6]=8.6 names[6]="Alpha Five" dim i as n dim total_pie as n total_pie=0 for i=1 to 6 total_pie=total_pie+inputs[i] next dim shared slice[6] as n for i=1 to 6 slice[i]=inputs[i]/total_pie next
Script 5. The OnInit code.
The OnDraw code follows. I created a new function, rgb(), to produce a color from a mix of blue, green, and red:
' these are the values from OnInit
dim shared slice[6] as n
dim shared inputs[6] as n
dim shared names[6] as c
' text alignment constants used in SetTextAlign WinAPI function
constant TA_BASELINE = 24 'The reference point will be on the
'baseline of the text.
constant TA_BOTTOM = 8 'The reference point will be on the
'bottom edge of the bounding rectangle of the text.
constant TA_CENTER = 6 'The reference point will be horizontally
'centered along the bounding rectangle of the text.
constant TA_LEFT = 0 'The reference point will be on the left edge of
'the bounding rectangle of the text.
constant TA_NOUPDATECP = 0 'Do not set the current point to the
'reference point.
constant TA_RIGHT = 2 'The reference point will be on the
'right edge of the bounding rectangle of the text.
constant TA_RTLREADING = 256 'Display the text right-to-left
'(if the font is designed for right-to-left reading).
constant TA_TOP = 0 'The reference point will be on the top edge of
'the bounding rectangle of the text.
constant TA_UPDATECP = 1 'Set the current point to the reference point.
'here is the color-creating function
function RGB as n(blue as n, green as n, red as n)
RGB=256*256*blue + 256*green + red
end function
declare gdi32 SetTextAlign LLL
declare gdi32 TextOut@TextOutA LLLLCL
Declare gdi32 Pie LLLLLLLLLL
xsize=A_USER.DRAW.RIGHT-A_USER.DRAW.LEFT
ysize=A_USER.DRAW.BOTTOM-A_USER.DRAW.TOP
Declare gdi32 Rectangle LLLLLL
hbrush = CreateSolidBrush((((128*256)+128)*256)+128)
oldbrush = SelectObject(A_USER.DRAW.HDC,hbrush)
' Clear the user object's background rectangle
Rectangle( A_USER.DRAW.HDC , A_USER.DRAW.LEFT , A_USER.DRAW.TOP,\
A_USER.DRAW.RIGHT , A_USER.DRAW.BOTTOM )
SelectObject(A_USER.DRAW.HDC,oldbrush)
DeleteObject(hbrush)
radius = sqrt((xsize/2)*(xsize/2)+(ysize/2)*(ysize/2))
dim color[6] as n
color[1]=rgb(0,0,150)
color[2]=rgb(0,150,0)
color[3]=rgb(150,0,0)
color[4]=rgb(0,75,75)
color[5]=rgb(75,0,75)
color[6]=rgb(75,75,0)
draw.hdc=A_USER.DRAW.HDC
piece_of_pie=0
for i=1 to 6
hbrush = CreateSolidBrush(color[i])
SelectObject(draw.hdc, hbrush)
prior_pie=piece_of_pie
x1coordinate=xsize/2+radius*cos(2*math_pi*prior_pie)
y1coordinate=xsize/2-radius*sin(2*math_pi*prior_pie)
new_pie=slice[i]
piece_of_pie=prior_pie+new_pie
Pie(draw.hdc, 0,0, a_user.draw.right, a_user.draw.bottom,\
x1coordinate, y1coordinate, \
xsize/2+radius*cos(2*math_pi*piece_of_pie), \
xsize/2-radius*sin(2*math_pi*piece_of_pie))
DeleteObject(hbrush)
next
piece_of_pie=0
for i=1 to 6
prior_pie=piece_of_pie
new_pie=slice[i]
piece_of_pie=prior_pie+new_pie
place_to_mark=prior_pie+slice[i]/2
SetTextAlign(draw.hdc,TA_CENTER .or. TA_TOP .or. TA_NOUPDATECP)
text=names[i]
TextOut(draw.hDC, xsize/2+(xsize/2)*.6*cos(2*math_pi*place_to_mark),\
xsize/2-(xsize/2)*.6*sin(2*math_pi*place_to_mark), text, \
len(text))
next
Script 6. The OnDraw code.
If you'd like to see this graph in action, you can download a table containing this example.
1/25/00
Don't forget, we need your feedback to make this site better!