Network Graphs in Object Pascal

Steve discusses Object Pascal, focusing on how the language implements objects and methods. In the process, he implements a network graph, using objects that can be reused for other applications.


December 01, 1989
URL:http://www.drdobbs.com/tools/network-graphs-in-object-pascal/184408245

Figure 1


Copyright © 1989, Dr. Dobb's Journal

DEC89: NETWORK GRAPHS IN OBJECT PASCAL

NETWORK GRAPHS IN OBJECT PASCAL

Linked Lists as reusable objects

Steve Kienle

Steve has been a professional programmer since 1983 and has written several programs that are available on the CompuServe Macintosh forums, including the Turing Machine Editor and Face Manager programs. He can be reached at 2314 Waverly, Kalamazoo, MI 49007 or though CompuServe at 72330, 111.


The origins of OOP and the emergence of OOP features in a number of programming languages are well known. The proponents of Ada and Modula-2 claim that OOP is part of the design of these languages. C, the language of the systems developer, has also been caught up in the OOP wave, spawning C++ and Objective C. Most recently, the Apple-developed OOP dialect of Pascal has gained momentum now that Microsoft and Borland have both added support of objects into their respective Pascal offerings.

In this article, I will discuss how Object Pascal implements objects and methods, and I'll describe a program that creates a network graph. I'll also look back at the objects that were created in this program and give examples of how they can be reused in other programs. This article assumes that you are familiar with OOP in general, and that you have a knowledge of standard Macintosh data types and toolbox calls such as Point, Rect, LineTo, and FrameRect.

The Network Graph Program

The network graph program creates and manipulates a simple network graph. This program allows the user to create and remove vertices and edges, and to move vertices around. The graph is created in a window that allows scrolling in two directions, up to the current size of the page. The graph is created by using tools available in a pallet window. For simplicity, the program doesn't support such features as saving the graph to disk, and it doesn't support the Edit menu items.

The code for the section of the program that handles object types and method definitions is provided in Listing One (see page 104). The executable program, complete code, and associated MPW (Macintosh Programming Workbench) support files are available through DDJ or from the author. (See the author biography for more information -- Ed.)

OOP is really programming from the data up, rather than from the functions down. OOP strives to encapsulate the data of the problem and to define methods for manipulating that data. In the case of the network graph program, the first step in using OOP is to define the data object that you wish to create, which is a network graph. The mathematical definition for a graph is "an object created by two sets. The first set consists of vertices, and the second set contains edges." From this definition, we quickly derive three different objects: the graph, the vertices, and the edges.

Digging a bit deeper into the definition reveals that the vertices and edges are collected into sets. Pascal does not allow the use of nonscalars in sets. As a result, I used a linked list to implement a set, and added two more objects, GraphList and GraphNode, to support the linked list.

GraphNode is a fairly simple object -- it only has a Next link. GraphNode's methods include Draw, DrawAll, Erase, EraseAll, Free, and FreeAll. Free, which is similar to the example presented in the sidebar, erases the node and then frees it from memory. Draw and Erase are null and serve as place holders for the methods in the descendant objects.

DrawAll, EraseAll, and FreeAll deserve a bit more attention. They use the Next link to pass a message to the next GraphNode in the list, and then call the appropriate method. These methods use inherited code and allow only one method call to cause a complete re-draw, erasure, or freeing of a GraphList. Note that because the Next instance variable is a GraphNode, you can use Next in a reference to another method call. This approach is shown in the SELF.Next .EraseAll type statements, which read as: "Look at myself, get the Next instance variable's value, and send that object instance the EraseAll message."

More Details.

GraphList holds the head pointer of the GraphNode link list. This object handles the special case of removing a GraphNode from the list, and also demonstrates code reusability. Because we need a list of Vertices and Edges, the creation of a GraphList object class creates one common location for the list-manipulation routines. The GraphList object only contains a FirstNode instance variable. The methods include AddNode, RemoveNode, Draw, Erase, and Free.

Draw and Erase make the process of redrawing a complete graph easier. These methods call DrawAll and EraseAll for FirstNode. Free also calls FreeAll for FirstNode, but then calls the inherited Free method in order to destroy the list object instance. AddNode and RemoveNode perform the manipulations on the list. AddNode takes as an argument a GraphNode object instance and makes this object the new FirstNode. RemoveNode also takes a GraphNode object instance as an argument. RemoveNode finds and removes the appropriate GraphNode from the list.

Note that the Vertex object type, which is maintained in a list, is based upon the GraphNode object type. Therefore, Vertex inherits the Next instance variable and the Draw, DrawAll, Erase, EraseAll, Free, and FreeAll methods. Draw and Erase must then be overridden. Examine these overridden methods and note that information about where the Vertex is centered and how large the Vertex should be made is required. For the network graph program, the Vertex is a circle with a radius of 10. This creates one new instance variable called Center. Draw and Erase now create or erase the 10-point radius circle around Center. You could set the Center instance variable by accessing the instance variable itself. But in order to support the OOP methodology as best as possible, I've added a SetCenter method.

Edge is directly related to the Vertex object type. Edge connects two Vertices. As a result, I've added two new instance variables, FromVertex and ToVertex. As with Vertex, Edge is maintained in a list and GraphNode is the base object. Again, Draw and Erase have been overridden. Draw draws a line from the center of FromVertex to the center of ToVertex. To keep the Vertex objects looking clean, I modified the Vertex Draw method to erase the circle and then frame it; the edges now start and end at the edge of the vertex frame. Erase sets the drawing pattern to white, and then calls Draw. This approach allows Draw to be modified in descendant objects, while Erase continues to work unchanged.

Edge also uses the SetFrom and SetTo methods to erase the old Edge, and to redraw the FromVertex or ToVertex to which it is attached. Edge is dependent upon two Vertex instances, so if either of those instances move, Edge must be redrawn. An apparently simple solution would be to modify the Vertex SetCenter method. But Vertex has no knowledge of which Edge instances are connected to it, so trying to locate, erase, and redraw those Edge instances would force external knowledge into the object. Therefore, the Graph object, which has knowledge of Edges and Vertices, handles this problem.

The Graph object is based upon TObject. Graph consists of two sets, so it needs instance variables that point to its list of vertices and edges. We can start the method list with some obvious methods: Draw, Erase, Free (which is overridden), AddVertex, RemoveVertex, AddEdge, and RemoveEdge. Draw and Erase call the EdgeList and VertexList instances of both the GraphList object and the Draw and Erase messages. The order is important here, because I have designed the Draw methods for the Edges instances to be drawn first. Similarly, Free frees the EdgeList and then the VertexList.

With each event, the Macintosh provides the location at which the event occurred. This means that in the case of the AddVertex, AddEdge, RemoveEdge, and RemoveVertex, only the location of the mouse is available. Thus, these methods take a point as their argument. AddVertex adds a vertex at the specified location.

Note that because the AddNode method of the GraphList object takes a GraphNode as an argument, the Vertex object instance cannot be used directly in the call. This is a result of the strict type constraints of Pascal, rather than a result of how Object Pascal is implemented. This limitation can be overcome by typecasting the object instance. This concept, which is borrowed from C, allows the compiler to accept the technique of sending a Vertex object instance to AddNode. This trick is required because I defined a common GraphList object, rather than defining VertexList and EdgeList, which would be identical except for what they are linking. The trick is required for the compiler only; the object instance is not modified in any way.

AddEdge is very similar to AddVertex. Instead of taking a location, AddEdge takes two Vertex object instances and creates an Edge instance that links them together.

RemoveEdge removes an Edge object instance. This means that we need a way to determine the Edge instance at a given point. Look ahead a bit and note that RemoveVertex also needs to locate an object instance based upon a location. To do this, we add PtInNode and FindNode to the GraphNode object type and add FindNode to the GraphList object type. The methods belong here because both descendant object types require it. PtInNode returns True if the point is considered to be in the object instance, and returns False otherwise. GraphNode. FindNode checks if the point is in the present object instance. If the point is in the present object instance, GraphNode.FindNode returns itself; otherwise, the checking down the list. The GraphList.FindNode method initiates the search at the FirstNode of the list and returns the result.

In order to support PtInNode, we need to define the region covered by the object instance. I use the term "region" on purpose, because the Mac Toolbox has a routine, PtInRgn, that does the processing for us. To create this region, I added a new instance variable to the GraphNode object type, called HotRegion. The SetRegion method creates the appropriate region. In the case of the Vertex object, this region is the circle that the Vertex creates on the screen. In the case of the Edge object, a region is created that follows the line but is eight pixels wide.

The addition of HotRegion, which is location dependent, forces a change in some of the other methods. As the center of a Vertex instance or an end point of an Edge instance is set, the HotRegion must be recreated. I modified SetCenter, SetFrom, and SetTo to reflect this requirement. Note that the Free method of GraphNode frees the region.

After the support methods are added to GraphNode, Vertex, and Edge, we can return to RemoveEdge. This method first finds the Edge instance in which the point is located and removes that Edge from EdgeList.

The Remove Vertex method is similar to RemoveEdge, but requires some additional support in the GraphNode and Edge object types. RemoveVertex first finds the Vertex instance in which the point is located. The method then removes all Edge instances that are connected to that Vertex instance.

Finding all such instances can be done in this method or through a method on the Edge object. Because the list traversal logic has been isolated in the GraphNode object, I chose to add the finding logic into the GraphNode object but leave the instance removal in RemoveVertex. This approach involves the creation of Connected and FindConnected methods in the GraphNode object type. The FindConnected method also must be included in the GraphList object type. In GraphNode, Connected always returns False; but the Edge object overrides this and returns True if Edge is connected to the Vertex instance. FindConnected finds the first connected instance in the list.

These routines enable you to find and remove the Edge instances connected to a given Vertex instance. Once all the appropriate Edge instances are removed, the Vertex object instance itself can be removed.

At this point, the special processing required for SetCenter can also be implemented. To do this, a SetVertexCenter method was added to the Graph object type. The purpose of the additional processing beyond the Vertex. SetCenter call is to find all of the connected edges, and to both erase these edges before and then redraw them after the SetCenter call. FindConnected allows you to move through EdgeList and find all of the connected Edge instances. The complexity of adding the special processing to the Graph object type, rather than to the Vertex type, makes the program much cleaner and more self contained.

Two methods have been added to make the program conform more closely to the Macintosh interface guidelines. These methods allow a Vertex instance to be moved with the mouse and allow an Edge instance to be added with the mouse.

The step of implementing MoveVertex in the Graph object is fairly easy. Given a point from which the move is starting, find the Vertex at the point. Use the Macintosh Toolbox routine DragGrayRgn to let the user pull the HotRegion around the screen. I have separated the Mac-specific code into the DragRegion function, which is not included in the listings with this article (but is available online. If the drag is completed with a move, the new center of the Vertexinstance is determined, and the Graph.SetVertexCenter method is called to move the Vertex instance to the new location.

The code for LinkVertices is also straightforward. First, check the location to see if it contains a Vertex instance, and then call a DragGrayLine procedure to find where the mouse is released. If the mouse is released in a Vertex instance that is different from the first Vertex instance, then the Graph.Add-Edge routine is called to add the new Edge instance. DragGrayLine follows the mouse around the window, trailing a gray line between the current location and the starting location until the mouse button is released. DragGrayLine then returns the location of the mouse.

Reusable Objects

GraphNode implements a linked list with some graphing methods associated with it. GraphList maintains a list of GraphNode instances. The Vertex and Edge object types are based upon the GraphNode type, and implement the vertices and edges of the network graph. The Graph object is the actual network graph and provides methods to support and modify the graph instance.

As written, the Graph object is somewhat restrictive. At the least, it allows several instances of network graphs to be handled by the same program. Graph can also be used at the base for additional types of graphs, such as directed graphs, weighted graphs, and so on.

One type of network graph that I want to mention is the Cartesian graph, which is what most people think about when you say "graph." By placing vertices carefully, and adding edges that connect one vertex to the next one down the X axis, we can create a program that graphs a set of data points fairly quickly. By using several Graph-based objects, several sets of data can be plotted in the same window. At the same time a separation of the sets can be maintained, which is useful when data sets are edited independently.

Additional methods can be added to the Graph-based object to provide for greater functionality. A method of traversing the graph, based upon some algorithm, is an example of this approach. In fact, I've written a Turing Machine Editor that is based upon these objects.

Note that objects such as the Vertex object type can be used in several ways. Many of these uses are listed indirectly earlier this article during the discussion of the Graph object type. Vertex can also be used as a base object type. Naturally, you can change the way in which the object is drawn. This change can give rise to different ways of showing each vertex in the window. The drawing method can even be selected by an instance variable in order to allow a graph to contain several different types of vertices. If you create a Vertex-based class that doesn't draw at all, then you have a way to generate line art. Note that many of these uses for Vertex type objects can be implemented within or without the Graph object itself.

Edge is intimately tied to the Vertex class, so Edge can only be used in conjunction with that type. Still, variations on this class can be useful in themselves. Such variations could include different ways of drawing the edge, such as drawing the edge as an arc or a gray bar.

Finally, the GraphList and GraphNode classes actually implement a list structure for objects that are drawn, connected, and selected, so these classes provide many opportunities for reuse. Any program that creates or manipulates lists of graphical objects, such as an object-oriented drawing program, is a candidate for use of the GraphList and GraphNode object types.

Object Pascal

In order to add OOP to Pascal, Apple modified Pascal to create Object Pascal (or Clascal -- Class Pascal -- as it was first called). These changes were made on the Pascal that was designed for the graphical-interface-based computer Apple was developing at the time: The Lisa. Even though the Lisa no longer exists, Object Pascal continues to gain momentum.

The major change to Pascal in Object Pascal is the addition of the Object type. In Object Pascal, the classes of OOP are defined in the Type declaration section as belonging to the Object type. The format for the Object statement is similar to the format for the Record statement, but the declaration of the Object statement contains two additional parts: the Base Object and the Method declarations. The format is shown in the railroad diagram in Figure 1.

The Base Object enables the use of class inheritance in Object Pascal. To make a new object type a descendant of another object type, place the parent's object type name into the parentheses.

The method declarations in Object Pascal are similar to the forward procedure and function declarations in Pascal. List the procedure or function declarations in the block, which is where new methods on this object must be declared. Optionally, an Override statement may follow a method declaration in order to replace an inherited method.

Most implementations of Object Pascal include a base object type that provides some simple methods. For example, my Pascal compiler calls its base object TObject. TObject provides base methods for Clone and Free. Clone makes a copy of an object instance, and Free frees an object instance from memory.

The code for each Object's methods must be included somewhere in the code section of the program. This is handled in a similar fashion to other procedure and function definitions, except that the procedure or function name is prefixed with the Object Type name. Whenever instance variables are used in method definitions, the variables are prefixed with the special SELF reference. This prefix tells the compiler to use the values for those variables in the object instance that called this method. Listing Two (page 108) provides a sample Circle class's type declaration and procedure definitions. Note that Circle inherits Clone and Free from TObject.

You declare a variable that is to be used as an instance of the Object type in the same way that you declare a Record variable. The catch is that until a new instance is created, that variable doesn't reference a valid object instance. The standard Pascal NEWO procedure is used for creating an object instance. Once an object instance is created, the instance variables are referenced in the same way that Record variables are referenced. To call an object's method, prefix the method name with the instance variable. An example of declaring and using a Circle object instance is shown in Listing Three, page 108.

As the example program stands now, the circle will be drawn in a window even after the instance is freed. If this result is not desired two options are available: Add a call to Circle.Erase before the call to Circle.Free, or override the Free inherited from the TObject class. To implement the second choice, use the Override statement in the method declaration section of the Circle Object Type definition.

Override provides a way to change an inherited method. Object Pascal also allows the new method to call the inherited method through the Inherited statement. The use of the Inherited statement allows small changes to be made to a method, while leaving the inherited code available and contained in the parent object. Listing Four, page 108, adds a Free method to the Circle Object Type and shows how to use the Override and Inherited statements. Note the use of the SELF reference here to call another method for the same object instance. These changes cause the example program to erase the circle when the instance is freed.

Through the use of inheritance and overriding, several related object types can have the same procedure name, such as Draw, and implement the procedure differently. This factor alone is a benefit, but it's not the only strength of Object Pascal. Object Pascal also links each object instance to the object type methods so that it's possible to create methods in the ancestor of several types that can be implemented without making changes in the children. Listing Five (see page 108) shows an example of this.

You may already be noticing that Object Pascal does not support data hiding. This failure follows from the way that Pascal itself was designed. While the newer versions of Pascal provide some indirect data hiding through the use of Units, the instance variables are still available to the calling program. While I admit the importance of data hiding, I don't feel an impact from its loss in Object Pascal. A lot of the benefits of data hiding can be realized through careful programming practices. -- S.K.

_NETWORK GRAPHS IN OBJECT PASCAL_ by Steven Kienle

[LISTING ONE]



TYPE
   GraphNode =
      OBJECT(TObject)
         Next: GraphNode;
         HotRegion: RgnHandle;

         PROCEDURE Initialize;
            { Drawing Methods }
         PROCEDURE Draw;
         PROCEDURE DrawAll;
         PROCEDURE Erase;
         PROCEDURE EraseAll;
            { Location Methods }
         PROCEDURE SetRegion;
         FUNCTION PtInNode(Where: Point): Boolean;
         FUNCTION FindNode(Where: Point): GraphNode;
         FUNCTION Connected(Which: GraphNode): Boolean;
         FUNCTION FindConnected(Which: GraphNode): GraphNode;
            { Freeing Methods }
         PROCEDURE Free; OVERRIDE;
         PROCEDURE FreeAll;
      END;

   GraphList =
      OBJECT(TObject)
         FirstNode: GraphNode;
         PROCEDURE Initialize;
            { Drawing Methods }
         PROCEDURE Erase;
         PROCEDURE Draw;
            { GraphList Manipulation Methods }
         PROCEDURE AddNode(Which: GraphNode);
         PROCEDURE RemoveNode(Which: GraphNode);
            { Location Methods }
         FUNCTION FindNode(Where: Point): GraphNode;
         FUNCTION FindConnected(Which: GraphNode): GraphNode;
            { Freeing Methods }
         PROCEDURE Free; OVERRIDE;
      END;

   Vertex =
      OBJECT(GraphNode)
         Center: Point; { Location of Vertex }
         PROCEDURE Initialize; OVERRIDE;
            { Drawing Methods }
         PROCEDURE Draw; OVERRIDE;
         PROCEDURE Erase; OVERRIDE;
            { Location Methods }
         PROCEDURE SetRegion; OVERRIDE;
         PROCEDURE SetCenter(thePoint: Point);
      END;

   Edge =
      OBJECT(GraphNode)
         FromVertex: Vertex; { End Points of Edge }
         ToVertex: Vertex;
         PROCEDURE Initialize; OVERRIDE;
            { Drawing Methods }
         PROCEDURE Draw; OVERRIDE;
         PROCEDURE Erase; OVERRIDE;
            { Location Methods }
         PROCEDURE SetRegion; OVERRIDE;
         FUNCTION Connected(Which: GraphNode): Boolean;
OVERRIDE;
         PROCEDURE Edge.SetFrom(Which: Vertex);
         PROCEDURE Edge.SetTo(Which: Vertex);
      END;

   Graph =
      OBJECT(TObject)
         VertexList: GraphList;
         EdgeList: GraphList;
         PROCEDURE Initialize;
            { Drawing Methods }
         PROCEDURE Draw;
         PROCEDURE Erase;
            { Manipulation Routines }
         PROCEDURE AddVertex(Where: Point);
         PROCEDURE AddEdge(FromWhich, ToWhich: Vertex);
         PROCEDURE RemoveVertex(Where: Point);
         PROCEDURE RemoveEdge(Where: Point);
         PROCEDURE SetVertexCenter
                 (Which: Vertex; Where: Point);
            { Macintosh Support Routines }
         PROCEDURE MoveVertex(Start: Point);
         PROCEDURE LinkVertices(Start: Point);
            { Freeing Method }
         PROCEDURE Free; OVERRIDE;
      END;

{ -------------- GraphNode Methods -------------- }
PROCEDURE GraphNode.Initialize;
   BEGIN
      SELF.Next := NIL;
      SELF.HotRegion := NIL;
   END;

PROCEDURE GraphNode.Draw;
   BEGIN
   END;

PROCEDURE GraphNode.DrawAll;
   BEGIN
      IF SELF.Next <> NIL THEN
         SELF.Next.DrawAll; { Draw next GraphNode }
      SELF.Draw; { Draw this GraphNode }
   END;

PROCEDURE GraphNode.Erase;
   BEGIN
   END;

PROCEDURE GraphNode.EraseAll;
   BEGIN
      IF SELF.Next <> NIL THEN
         SELF.Next.EraseAll; { Erase next GraphNode }
      SELF.Erase; { Erase this GraphNode }
   END;

PROCEDURE GraphNode.SetRegion;
   BEGIN
      IF SELF.HotRegion <> NIL THEN { Drop old Region }
         DisposeRgn(SELF.HotRegion);
      SELF.HotRegion := NewRgn; { Allocate a new one }
   END;

FUNCTION GraphNode.PtInNode(Where: Point): Boolean;
   BEGIN
      IF PtInRgn(Where, SELF.HotRegion) THEN
         PtInNode := True
      ELSE
         PtInNode := False;
   END;

FUNCTION GraphNode.FindNode(Where: Point): GraphNode;
   BEGIN
      IF SELF.PtInNode(Where) THEN { It's here }
         FindNode := SELF
      ELSE IF SELF.Next = NIL THEN { There are none }
         FindNode := NIL
      ELSE { Try the Next }
         FindNode := SELF.Next.FindNode(Where);
   END;

FUNCTION GraphNode.Connected(Which: GraphNode): Boolean;
   BEGIN
      Connected := False;
   END;

FUNCTION GraphNode.FindConnected(Which: GraphNode): GraphNode;
   BEGIN
      IF SELF.Connected(Which) THEN { Is this connected }
         FindConnected := SELF
      ELSE IF SELF.Next = NIL THEN { There are none }
         FindConnected := NIL
      ELSE { Try the Next }
         FindConnected := SELF.Next.FindConnected(Which);
   END;

PROCEDURE GraphNode.Free;
   BEGIN
      IF SELF.HotRegion <> NIL THEN { Free Region Space }
         DisposeRgn(SELF.HotRegion);
      SELF.Erase; { Erase then Free }
      INHERITED Free;
   END;

PROCEDURE GraphNode.FreeAll;
   BEGIN
      IF SELF.Next <> NIL THEN { Free the next GraphNode }
         SELF.Next.FreeAll;
      SELF.Free; { Then Free this GraphNode }
   END;

{ -------------- GraphList Methods -------------- }
PROCEDURE GraphList.Initialize;
   BEGIN
      SELF.FirstNode := NIL;
   END;

PROCEDURE GraphList.Erase;
   BEGIN
      IF SELF.FirstNode <> NIL THEN
         SELF.FirstNode.EraseAll; { Erase the GraphList }
   END;

PROCEDURE GraphList.Draw;
   BEGIN
      IF SELF.FirstNode <> NIL THEN
         SELF.FirstNode.DrawAll; { Draw the GraphList }
   END;

PROCEDURE GraphList.AddNode(Which: GraphNode);
   BEGIN
      Which.Next := SELF.FirstNode; { Link Which in GraphList }
      SELF.FirstNode := Which;
   END;

PROCEDURE GraphList.RemoveNode(Which: GraphNode);
   VAR
      Check: GraphNode;
   BEGIN
      { If it is the head GraphNode, relink the Head }
      IF SELF.FirstNode = Which THEN
         SELF.FirstNode := Which.Next
      ELSE BEGIN
         { Otherwise look for Which GraphNode }
         Check := SELF.FirstNode;
         WHILE (Check <> NIL) DO BEGIN
            { If Which is found, remove it from GraphList }
            IF Check.Next = Which THEN
               Check.Next := Which.Next;
            Check := Check.Next;
         END;
      END;

      Which.Free; { Free this node }
   END;

FUNCTION GraphList.FindNode(Where: Point): GraphNode;
   BEGIN { Find the Node at this location }
      IF SELF.FirstNode <> NIL THEN
         FindNode := SELF.FirstNode.FindNode(Where)
      ELSE
         FindNode := NIL;
   END;

FUNCTION GraphList.FindConnected(Which: GraphNode): GraphNode;
   BEGIN { Find the Node connected to this one }
      IF SELF.FirstNode <> NIL THEN
         FindConnected := SELF.FirstNode.FindConnected(Which)
      ELSE
         FindConnected := NIL;
   END;

PROCEDURE GraphList.Free;
   BEGIN
      IF SELF.FirstNode <> NIL THEN
         SELF.FirstNode.FreeAll; { Free the Nodes }
      INHERITED Free; { Free GraphList }
   END;

{ -------------- Vertex Methods -------------- }
PROCEDURE Vertex.Initialize;
   BEGIN
      INHERITED Initialize;
      SELF.Center.h := 0;
      SELF.Center.v := 0;
   END;

PROCEDURE Vertex.Draw;
   VAR
      theRect: Rect;
   BEGIN
      SELF.Erase; { Erase Vertex Area }
      { Set up Rectangle }
      theRect.top := SELF.Center.v - 10;
      theRect.left := SELF.Center.h - 10;
      theRect.bottom := SELF.Center.v + 10;
      theRect.right := SELF.Center.h + 10;
      { Draw Vertex }
      FrameOval(theRect);
   END;

PROCEDURE Vertex.Erase;
   VAR
      theRect: Rect;
   BEGIN
      { Set up Rectangle }
      theRect.top := SELF.Center.v - 10;
      theRect.left := SELF.Center.h - 10;
      theRect.bottom := SELF.Center.v + 10;
      theRect.right := SELF.Center.h + 10;
      { Erase Vertex }
      EraseOval(theRect);
   END;

PROCEDURE Vertex.SetRegion;
   BEGIN
      INHERITED SetRegion; { Do default processing }
      OpenRgn; { Create new region area }
      SELF.Draw;
      CloseRgn(SELF.HotRegion);
   END;

PROCEDURE Vertex.SetCenter(thePoint: Point);
   BEGIN
      SELF.Erase; { Erase Vertex at old Center }
      SELF.Center := thePoint; { Set the Center }
      SELF.Draw; { Draw Vertex at new Center }
      SELF.SetRegion; { Reset HotRegion }
   END;

{ -------------- Edge Methods -------------- }
PROCEDURE Edge.Initialize;
   BEGIN
      INHERITED Initialize;
      FromVertex := NIL;
      ToVertex := NIL;
   END;

PROCEDURE Edge.Draw;
   VAR
      Where: Point;
   BEGIN
      IF (SELF.FromVertex <> NIL) AND (SELF.ToVertex <> NIL) THEN
BEGIN
         { Start in center of FromVertex }
         Where := SELF.FromVertex.Center;
         MoveTo(Where.h, Where.v);
         { Draw line to center of ToVertex }
         Where := SELF.ToVertex.Center;
         LineTo(Where.h, Where.v);
      END;
   END;

PROCEDURE Edge.Erase;
   VAR
      pnState: PenState;
   BEGIN
      GetPenState(pnState); { Save current settings }
      PenPat(white); { Set color & Draw to erase }
      SELF.Draw;
      SetPenState(pnState); { Reset settings }
      SELF.FromVertex.Draw; { Redraw affected Vertices }
      SELF.ToVertex.Draw;
   END;

PROCEDURE Edge.SetRegion;
   BEGIN
      INHERITED SetRegion; { Do default processing }
      OpenRgn; { Create new region area }
         MoveTo(SELF.FromVertex.Center.h + 4,
SELF.FromVertex.Center.v + 4);
      LineTo(SELF.ToVertex.Center.h + 4, SELF.ToVertex.Center.v + 4);
      LineTo(SELF.ToVertex.Center.h - 4, SELF.ToVertex.Center.v - 4);
         LineTo(SELF.FromVertex.Center.h - 4,
SELF.FromVertex.Center.v - 4);
         LineTo(SELF.FromVertex.Center.h + 4,
SELF.FromVertex.Center.v + 4);
      CloseRgn(SELF.HotRegion);
   END;

FUNCTION Edge.Connected(Which: GraphNode): Boolean;
   BEGIN
      IF (SELF.FromVertex = Which) OR (SELF.ToVertex = Which) THEN
         Connected := True
      ELSE
         Connected := False;
   END;

PROCEDURE Edge.SetFrom(Which: Vertex);
   BEGIN
      IF (SELF.FromVertex <> NIL) AND (SELF.ToVertex <> NIL) THEN
BEGIN
         { Erase old edge and redraw unlinked Vertex }
         SELF.Erase;
         SELF.FromVertex.Draw;
      END;
      SELF.FromVertex := Which;
      IF (SELF.FromVertex <> NIL) AND (SELF.ToVertex <> NIL) THEN
BEGIN
         { Draw new edge and redraw linked Vertices }
         SELF.Draw;
         SELF.FromVertex.Draw;
         SELF.ToVertex.Draw;
         SELF.SetRegion; { Reset HotRegion }
      END;
   END;

PROCEDURE Edge.SetTo(Which: Vertex);
   BEGIN
      IF (SELF.FromVertex <> NIL) AND (SELF.ToVertex <> NIL) THEN
BEGIN
         { Erase old edge and redraw unlinked Vertex }
         SELF.Erase;
         SELF.ToVertex.Draw;
      END;
      SELF.ToVertex := Which;
      IF (SELF.FromVertex <> NIL) AND (SELF.ToVertex <> NIL) THEN
BEGIN
         { Draw new edge and redraw linked Vertices }
         SELF.Draw;
         SELF.FromVertex.Draw;
         SELF.ToVertex.Draw;
         SELF.SetRegion; { Reset HotRegion }
      END;
   END;

{ -------------- Graph Methods -------------- }
PROCEDURE Graph.Initialize;
   BEGIN
      New(SELF.VertexList);
      SELF.VertexList.Initialize;
      New(SELF.EdgeList);
      SELF.EdgeList.Initialize;
   END;

PROCEDURE Graph.Draw;
   BEGIN
      IF SELF.EdgeList <> NIL THEN
         SELF.EdgeList.Draw;
      IF SELF.VertexList <> NIL THEN
         SELF.VertexList.Draw;
   END;

PROCEDURE Graph.Erase;
   BEGIN
      SELF.EdgeList.Erase;
      SELF.VertexList.Erase;
   END;

PROCEDURE Graph.AddVertex(Where: Point);
   VAR
      NewVertex: Vertex;
   BEGIN
      { Create and initialize a new Vertex at Where }
      New(NewVertex);
      NewVertex.Initialize;
      NewVertex.SetCenter(Where);
      { Add new vertex to list, typecasting is required }
      SELF.VertexList.AddNode(GraphNode(NewVertex));
   END;


PROCEDURE Graph.RemoveVertex(Where: Point);
   VAR
      WhichEdge: GraphNode;
      WhichVertex: GraphNode;
   BEGIN
      { Find the appropriate Node }
      WhichVertex := SELF.VertexList.FindNode(Where);
      { If it exists... }
      IF WhichVertex <> NIL THEN BEGIN
         REPEAT
            { Find Edges Connected to the Vertex }
            WhichEdge :=
SELF.EdgeList.FindConnected(WhichVertex);
            { If an Edge exists, remove it }
            IF WhichEdge <> NIL THEN
               SELF.EdgeList.RemoveNode(WhichEdge);
         UNTIL (WhichEdge = NIL);
         { Finally, remove the Vertex }
         SELF.VertexList.RemoveNode(WhichVertex);
      END;
   END;

PROCEDURE Graph.AddEdge(FromWhich, ToWhich: Vertex);
   VAR
      NewEdge: Edge;
   BEGIN
      { Create and initialize a new Vertex at Where }
      New(NewEdge);
      NewEdge.Initialize;
      NewEdge.SetFrom(FromWhich);
      NewEdge.SetTo(ToWhich);
      { Add new vertex to list, typecasting is required }
      SELF.EdgeList.AddNode(GraphNode(NewEdge));
   END;

PROCEDURE Graph.RemoveEdge(Where: Point);
   VAR
      WhichEdge: GraphNode;
   BEGIN
      { Find the appropriate Node }
      WhichEdge := SELF.EdgeList.FindNode(Where);
      { If it exists, remove it }
      IF WhichEdge <> NIL THEN
         SELF.EdgeList.RemoveNode(WhichEdge);
   END;

PROCEDURE Graph.SetVertexCenter(Which: Vertex; Where: Point);
   VAR
      anEdge: Edge;
   BEGIN
      { Move through the EdgeList finding Connected Instances}
      anEdge := Edge(SELF.EdgeList.FindConnected(GraphNode(Which)));
      WHILE (anEdge <> NIL) DO BEGIN
         anEdge.Erase; { Erase them and move on }
         IF anEdge.Next <> NIL THEN
            anEdge :=
Edge(anEdge.Next.FindConnected(GraphNode(Which)))
         ELSE
            anEdge := NIL;
      END;
      Which.SetCenter(Where); { Set the Vertex instance's center }
      { Move through the EdgeList finding Connected Instances}
      anEdge := Edge(SELF.EdgeList.FindConnected(GraphNode(Which)));
      WHILE (anEdge <> NIL) DO BEGIN
         anEdge.Draw; { Draw them and their vertices; move on }
         anEdge.FromVertex.Draw;
         anEdge.ToVertex.Draw;
         IF anEdge.Next <> NIL THEN
            anEdge :=
Edge(anEdge.Next.FindConnected(GraphNode(Which)))
         ELSE
            anEdge := NIL;
      END;
   END;

PROCEDURE Graph.MoveVertex(Start: Point);
   VAR
      Displacement: Point;
      NewCenter: Point;
      WhichVertex: Vertex;
   BEGIN
      WhichVertex := Vertex(SELF.VertexList.FindNode(Start));
         { If the vertex is moved, find the new center and
             place the Vertex and redraw affected Edges }
      IF WhichVertex <> NIL THEN
          IF DragRegion(WhichVertex.HotRegion, Start, Displacement.h,
                    Displacement.v) THEN BEGIN
            NewCenter := WhichVertex.Center;
            AddPt(Displacement, NewCenter);
            SELF.SetVertexCenter(WhichVertex, NewCenter);
         END;
   END;

PROCEDURE Graph.LinkVertices(Start: Point);
   VAR
      FirstVertex: Vertex;
      LastVertex: Vertex;
      Stop: Point;
   BEGIN
      { Find the FromVertex }
      FirstVertex := Vertex(SELF.VertexList.FindNode(Start));
      IF FirstVertex <> NIL THEN BEGIN
                    DragGrayLine(Start, Stop); { Drag a line around }
          { Find the ToVertex }
          LastVertex := Vertex(SELF.VertexList.FindNode(Stop));
          IF (LastVertex <> NIL) AND (FirstVertex <> LastVertex) THEN
            SELF.AddEdge(FirstVertex, LastVertex);
      END;
   END;

PROCEDURE Graph.Free;
   BEGIN
      SELF.EdgeList.Free;
      SELF.VertexList.Free;
      INHERITED Free;
   END;






[LISTING TWO]



Type
   Circle = Object (TObject)       { The Circle class declaration }
         { Instance Variables }
      Center : Point ;      { The Center of the Circle }
      Radius : Integer ;      { The Radius of the Circle }
         { Methods }
      Procedure Draw ;      { Draw the Circle }
      Procedure Erase ;      { Erase the Circle }
   end ;
Procedure Circle.Draw ;
   Var
      theRect : Rect ;       { Rectangular area of the Circle }
   Begin
         { Set up the Rectangle }
      theRect.top := SELF.Center.v - SELF.Radius ;
      theRect.left := SELF.Center.h - SELF.Radius ;
      theRect.bottom := SELF.Center.v + SELF.Radius ;
      theRect.right := SELF.Cener.h + SELF.Radius ;
      FrameOval (theRect) ;      { Draw it }
   End ;
Procedure Circle.Erase ;
   Var
      theRect : Rect ;      { Rectangular area of the Circle }
   Begin
         { Set up the Rectangle }
      theRect.top := SELF.Center.v - SELF.Radius ;
      theRect.left := SELF.Center.h - SELF.Radius ;
      theRect.bottom := SELF.Center.v + SELF.Radius ;
      theRect.right := SELF.Cener.h + SELF.Radius ;

      EraseOval (theRect) ;         { Erase it }
   End ;






[LISTING THREE]


Program DrawtheCircle ;

< Circle's type declaration >
Var
   aCircle : Circle ;

< Circle's method definitions >
Begin
   new(aCircle) ;            { Get a new instance }

   aCircle.Center.h := 50 ;      { Set up instance variables }
   aCircle.Center.v := 50 ;
   aCirlce.Radius := 50 ;

   aCircle.Draw ;         {Draw it and free it }
   aCircle.Free ;
End.





[LISTING FOUR]


Type
   Circle = Object (TObject)      { The Circle class declaration }
         { Instance Variables }
      Center : Point ;      { The Center of the Circle }
      Radius : Integer ;      { The Radius of the Circle }
         { Methods }
      Procedure Draw ;      { Draw the Circle }
      Procedure Erase ;      { Erase the Circle }
      Procedure Free ; Override ; { The Free method needs changes }
   end ;

Procedure Circle.Draw ;
   as before
Procedure Circle.Erase ;
   as before
Procedure Cirlce.Free ;
   Begin
      SELF.Erase ;
      Inherited Free ;
   End ;





[LISTING FIVE]

[Listing Four]

Type
   DrawObject = Object (TObject)   { The DrawObject class declaration }
         { Instance Variables }
        Location : Point ;      { The location of the Object }
         { Methods }
        Procedure Draw ;         { Draw the Object }
        Procedure Erase ;         { Erase the Object }
        Procedure Offset (dh, dv : Integer) ; { Offset Object by dh, dv }
        Procedure Free ; Override ;     { The Free method needs changes }
   end ;
   Circle = Object (DrawObject)         { The Circle class declaration }
         { Instance Variables }
      Radius : Integer ;             { The Radius of the Circle }
         { Methods }
      Procedure Draw ; Override ;      { Draw the Circle }
      Procedure Erase ; Override ;      { Erase the Circle }
   end ;
   Rectangle = Object (DrawObject)   { The Rectangle class declaration }
         { Instance Variables }
      horSize : Integer ;   { The Horizontal Size of the Rectangle }
      verSize : Integer ;      { The Verical Size of the Rectangle }
         { Methods }
      Procedure Draw ; Override ;      { Draw the Rectangle }
      Procedure Erase ; Override ;      { Erase the Rectangle }
   end ;

{ --------------- The DrawObject Methods --------------- }
Procedure DrawObject .Draw ;
   Begin
   End ;
Procedure DrawObject .Erase ;
   Begin
   End ;
Procedure DrawObject .Offset (dh, dv : Integer) ; { Offset Object by dh, dv }
   Begin
      SELF.Erase ;        { Erase Object at its present location }
         { Change the location of the Object }
      SELF.Location.h := SELF.Location.h + dh ;
      SELF.Location.v := SELF.Location.v + dv ;
      SELF.Draw ;      { Draw Object at its new location }
   End ;
Procedure DrawObject.Free ;
   Begin
      SELF.Erase ;
      Inherited Free ;
   End ;

{ --------------- The Circle Methods --------------- }
Procedure Circle.Draw ;
   as before
Procedure Circle.Erase ;
   as before

{ --------------- The Rectangle Methods --------------- }
Procedure Rectangle .Draw ;
   Var
      theRect : Rect ;   { Rectangular area of the Circle }
   Begin
         { Set up the Rectangle }
      theRect.top := SELF.Location.v ;
      theRect.left := SELF.Location.h ;
      theRect.bottom := SELF.Location.v + SELF.verSize ;
      theRect.right := SELF.Location.h + SELF.horSize ;
      FrameRect (theRect) ;      { Draw it }
   End ;
Procedure Circle.Erase ;
   Var
      theRect : Rect ;       { Rectangular area of the Circle }
   Begin
         { Set up the Rectangle }
      theRect.top := SELF.Location.v ;
      theRect.left := SELF.Location.h ;
      theRect.bottom := SELF.Location.v + SELF.verSize ;
      theRect.right := SELF.Location.h + SELF.horSize ;
      EraseRect (theRect) ;      { Draw it }
   End ;












Copyright © 1989, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.