Line and Bar Charts in Alpha Five

by Dr. Peter Wayne

In a previous article, I described how you can use the Windows API to produce attractive pie graphs in Alpha Five. This article extends those principles to produce line and bar graphs.

Some New WinAPI functions

The WinAPI functions we will use are:

I have found that the 2 filled functions, Polygon and Rectangle, work quite well in A5v4, but that Polyline is temperamental in v4. It is well-behaved in in the beta version of A5v5.

The Filled Line Graph (Polygon)

The definition of Polygon in Visual Basic is:

Declare Function Polygon Lib "gdi32.dll" (ByVal hdc As Long, lpPoint As POINT_TYPE, ByVal nCount As Long) As Long

Polygon draws and fills a polygon on a device. The polygon is drawn using the current brush.

hdc A device context to the device to draw the polygon on.
lpPoint An array holding the vertices of the polygon in the order which they will be drawn in. Specify each point only once.
nCount The number of elements in the array passed as lpPoint.
Polygon

Figure 1. A filled polygon.

Figure 1 shows a filled polygon used as a line graph. You should realize that any polygon can be programmed with this WinAPI call - you can, for example, code triangles, hexagons, or any irregular or regular polygon. The Xbasic declarations for the polygon are

declarestruct point L1X,L1Y
declarestruct points point100points
declare gdi32 Polygon LL(points)L

The first declarestruct establishes a new variable type in Alpha Five, the point, which is simply 2 32-bit "long" integers, X and Y. The second declarestruct creates an array of 100 points, that is, an array of 100 32-bit long integer X-Y pairs. The final declare tells A5 that there is a Windows API function, Polygon, found in gdi32.dll, that takes as arguments a long integer, the address of an array of points, and another long integer, and that returns a long integer. Once again, I refer you to Paul Kuliniewicz's excellent Visual Basic WinAPI site for a listing of useful WinAPI calls.

The only relevant actions in the User Object's event list are the OnInit event and the OnDraw event. For more information on the user object, see my previous article on Pie Charts. Here are the scripts for the 2 events:

declare gdi32 CreateSolidBrush LL
declare gdi32 SelectObject LLL
declare gdi32 DeleteObject LL
declare gdi32 SetTextAlign LLL 
declare gdi32 TextOutA LLLLCL
Declare gdi32 Rectangle LLLLLL
declarestruct point L1X,L1Y
declarestruct points point100points
declare gdi32 Polygon LL(points)L

Script 1. The OnInit script for the user object establishes the interfaces to the WinAPI.

And the OnDraw script:

''XBasic
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. 

dim num_points as n
t=table.open("pie.dbf",file_ro_shared)
t.index_primary_put()
t.fetch_first()
num_points=t.records_get()
dim inputs[num_points] as n
i=0
while .not. t.fetch_eof()
	i=i+1
	inputs[i]=t.input 
	t.fetch_next()
end while
t.close()

dim pts.points[num_points+2] as p

hbrush = CreateSolidBrush(rgb(255,255,255)) ' white
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)
hbrush=CreateSolidBrush(rgb(255,0,255)) ' red+blue 
oldbrush=SelectObject(a_user.draw.hdc,hbrush)

min_y=0
max_y=0
xscale_factor = (a_user.draw.right-a_user.draw.left)/(num_points-1)
for i=2 to num_points+1 
	pts.points[i].x=(i-2)*xscale_factor
	if inputs[i-1]>max_y then
		max_y=inputs[i-1] 
	end if
	if inputs[i-1]<min_y then
		min_y=inputs[i-1] 
	end if
next
pts.points[1].x=pts.points[2].x
pts.points[num_points+2].x=pts.points[num_points+1].x
yscale_factor=(a_user.draw.bottom-a_user.draw.top)/(max_y-min_y)/1.1  
' extra 10% to have a little whitespace above the tallest point
for i=2 to num_points+1
	pts.points[i].y=a_user.draw.bottom-yscale_factor*inputs[i-1]
next
pts.points[1].y=a_user.draw.bottom
pts.points[num_points+2].y=a_user.draw.bottom
draw.hdc=a_user.draw.hdc

Polygon(draw.hdc,pts,num_points+2)
DeleteObject(hbrush)

for i=2 to num_points+1 
	SetTextAlign(draw.hdc,TA_CENTER .or. TA_BOTTOM .or. TA_NOUPDATECP)
	text="|"
	TextOutA(draw.hDC, pts.points[i].x,\
	 a_user.draw.bottom, text, len(text)) 
next

Script2. The OnDraw script for the User Object.

Note that this OnDraw script reads the points to be plotted from a file (pie.dbf). The values I plotted are:

input data in pie.dbf

Figure 2. Pie.dbf has a single numeric field, input. The values in this field are used by Script 2 to create the filled Polygon graph.

The Line Graph (Polyline)

The Polyline API is almost identical to the Polygon API:

 Declare Function Polyline Lib "gdi32.dll" (ByVal hdc As Long, lpPoint As POINT_TYPE, ByVal nCount As Long) As Long

Polyline draws a series of lines on a graphics-capable device. These lines connect the points given in an array passed as lpPoint. Lines are drawn connecting the first point to the second point, the second point to the third point, etc. The first and last points are not connected. The lines are drawn using the device's current drawing color. The function returns 1 if successful, or 0 if an error occured.

I have not been able to change the device's "current drawing color" in Alpha Five, so all my line graphs show only black lines. In addition, the function works erratically in A5v4. In A5v5 the function produces output like this:

Line Graph

Figure 3. A line graph in A5v5.

The code for this graph is similar to the code for the filled polygon, but this time the points are not read from a table but are hard-coded into the OnInit script for the user object:

''XBasic
declare gdi32 CreateSolidBrush LL
declare gdi32 SelectObject LLL
declare gdi32 DeleteObject LL

dim shared inputs[6] as n 

inputs[1]=1.1
inputs[2]=2.2
inputs[3]=6.6
inputs[4]=4.4
inputs[5]=5.5
inputs[6]=3.3

declarestruct point L1X,L1Y

declarestruct points point100points

Script 3. The OnInit script for the line graph. The values to be plotted are hard-coded into the inputs[] array.

The line graph is drawn in the user object's OnDraw script:

''XBasic
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. 

dim shared inputs[6] as n
dim pts.points[8] as p

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 Rectangle LLLLLL
declare gdi32 Polyline LL(points)L

draw.hdc=a_user.draw.hdc
hbrush = CreateSolidBrush(rgb(255,255,255)) ' white


oldbrush = SelectObject(A_USER.DRAW.HDC,hbrush)
   ' Clear the control'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)

min_y=0
max_y=0
xscale_factor = (a_user.draw.right-a_user.draw.left)/5
for i=2 to 7 
	pts.points[i].x=(i-2)*xscale_factor
	if inputs[i-1]>max_y then
		max_y=inputs[i-1] 
	end if
	if inputs[i-1]<min_y then
		min_y=inputs[i-1] 
	end if
next
pts.points[1].x=pts.points[2].x
pts.points[8].x=pts.points[7].x
yscale_factor=(a_user.draw.bottom-a_user.draw.top)/(max_y-min_y)/1.1  
' extra 10% to have a little whitespace above the tallest point
for i=2 to 7
	pts.points[i].y=a_user.draw.bottom-yscale_factor*inputs[i-1]
next
pts.points[1].y=a_user.draw.bottom
pts.points[8].y=a_user.draw.bottom
draw.hdc=a_user.draw.hdc
Polyline(draw.hdc,pts,8)


for i=2 to 7 

SetTextAlign(draw.hdc,TA_CENTER .or. TA_BOTTOM .or. TA_NOUPDATECP)
	text="|"
	TextOut(draw.hDC, pts.points[i].x, \
	a_user.draw.bottom, text, len(text)) 
next

number_y_ticks=(a_user.draw.bottom-a_user.draw.top)/yscale_factor
for i=1 to yscale_factor
	text="---"
	TextOut(draw.hdc, 0, a_user.draw.top+(i*yscale_factor),\
	 text, len(text)) 
next

Script 4. The OnDraw script for the line graph. Note that here I included vertical ticks as well as horizontal ticks.

The Bar Graph

The bar graph is the simplest graph to program. The only fancy API call we have to use is Rectangle, a call with which we're already familiar - we use it to clear the background of the user object prior to drawing the graph. In this example I use different colors for each bar in the graph:

Bar Graph

Figure 4. This bar graph plots the same points as in Figure 1's filled line (polygon) graph.

The OnInit script is straightforward:

''XBasic
declare gdi32 CreateSolidBrush LL
declare gdi32 SelectObject LLL
declare gdi32 DeleteObject LL

Declare gdi32 Rectangle LLLLLL

Script 5. The OnInit script for the bar graph includes just a few WinAPI declarations.

The heavy lifting is done by the OnDraw script:

dim num_points as n
t=table.open("pie.dbf",file_ro_shared)
t.index_primary_put()
t.fetch_first()
num_points=t.records_get()
dim inputs[num_points] as n
i=0
while .not. t.fetch_eof()
	i=i+1
	inputs[i]=t.input 
	t.fetch_next()
end while
t.close()

maxy=0
for i=1 to num_points
	 if inputs[i]>maxy then 
	 	maxy=inputs[i]
	 end if
next

function RGB as n(blue as n, green as n, red as n)
	 RGB=256*256*blue + 256*green + red
end function

hbrush = CreateSolidBrush(rgb(255,255,255)) ' white

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 )
DeleteObject(hbrush)

xscale_factor = (a_user.draw.right-a_user.draw.left)/(num_points+1)
yscale_factor = (a_user.draw.bottom-a_user.draw.top)/(1.1*maxy)
draw.hdc=a_user.draw.hdc
width=0.5*xscale_factor
for i=1 to num_points 
	xstart=a_user.draw.left+(i*xscale_factor)-width/2
	xend=a_user.draw.left+(i*xscale_factor)+width/2
	top=a_user.draw.bottom-yscale_factor*inputs[i]
	hbrush=CreateSolidBrush(rgb(rand()*255,rand()*255,rand()*255)) 
	SelectObject(draw.hdc,hbrush)
	Rectangle(draw.hdc, xstart, top, xend, a_user.draw.bottom)
	DeleteObject(hbrush)
next

Script 6. The OnDraw script sizes, positions, and colors in the rectangles for each bar.

Notice that Script 6 employs the rand() function to choose colors for the bars.

These graphs are useful in themselves but you may be interested in them, as I was, as a way to explore the Windows API. Please let me know if you find other nifty ways to interface Alpha Five with the operating system and other programs. If you'd like to study these graphs, you can download a table containing these examples.

2/3/00

Don't forget, we need your feedback to make this site better!

Return to home